Merge "No-Display Activity to handle SIM Activation intent."
diff --git a/res/layout/emergency_dialer.xml b/res/layout/emergency_dialer.xml
index 5c721c5..a68f5de 100644
--- a/res/layout/emergency_dialer.xml
+++ b/res/layout/emergency_dialer.xml
@@ -16,10 +16,101 @@
 
 <!-- Layout for the emergency dialer; see EmergencyDialer.java. -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/top"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-    <include layout="@layout/dialpad_view" />
+        android:id="@+id/top"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingLeft="16dp"
+        android:paddingRight="16dp">
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="bottom"
+            android:orientation="vertical">
+
+        <!-- FrameLayout -->
+        <com.android.phone.EmergencyActionGroup
+                android:layout_height="64dp"
+                android:layout_width="match_parent"
+                android:layout_marginTop="16dp"
+                android:layout_marginBottom="24dp">
+
+            <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent">
+                <Button android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:id="@+id/action1"
+                        />
+                <Button android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:id="@+id/action2"
+                        />
+                <Button android:layout_width="0dp"
+                        android:layout_height="match_parent"
+                        android:layout_weight="1"
+                        android:id="@+id/action3"
+                        />
+            </LinearLayout>
+
+            <FrameLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:id="@+id/selected_container"
+                    android:visibility="invisible"
+                    android:focusable="true"
+                    android:clickable="true">
+
+                <View
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:backgroundTint="#ffe53935"
+                        android:focusable="false"
+                        android:clickable="false"
+                        style="?android:attr/buttonStyle"/>
+
+                <View
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:id="@+id/ripple_view"
+                        android:backgroundTint="#22000000"
+                        android:visibility="invisible"
+                        android:focusable="false"
+                        android:clickable="false"
+                        style="?android:attr/buttonStyle"/>
+
+                <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:orientation="vertical"
+                        android:focusable="false"
+                        android:clickable="false"
+                        android:backgroundTint="#00000000"
+                        style="?android:attr/buttonStyle">
+                    <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:id="@+id/selected_label"
+                            android:textAppearance="?android:attr/textAppearanceButton" />
+                    <TextView
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:id="@+id/launch_hint"
+                            android:text="@string/emergency_action_launch_hint"
+                            android:textStyle="italic" />
+                </LinearLayout>
+
+            </FrameLayout>
+
+        </com.android.phone.EmergencyActionGroup>
+
+        <include layout="@layout/dialpad_view_unthemed"
+                android:theme="@style/Dialpad_Dark" />
+
+    </LinearLayout>
+
     <FrameLayout
         android:id="@+id/floating_action_button_container"
         android:layout_width="@dimen/floating_action_button_width"
@@ -35,4 +126,5 @@
             android:contentDescription="@string/description_dial_button"
             android:src="@drawable/fab_ic_call"/>
     </FrameLayout>
+
 </FrameLayout>
diff --git a/res/layout/otacall_card.xml b/res/layout/otacall_card.xml
index 74b31e8..fea2077 100644
--- a/res/layout/otacall_card.xml
+++ b/res/layout/otacall_card.xml
@@ -115,7 +115,8 @@
                 android:layout_height="wrap_content"
                 android:orientation="vertical"
                 android:layout_marginTop="1dip"
-                android:visibility="gone" >
+                android:visibility="gone"
+                android:theme="@style/Dialpad_Light">
 
             <!-- Note there's no "dtmfDialerField" EditText here;
                  in the OTA UI there's no visible "digits" display
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 8971b4a..05089c2 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -555,4 +555,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Net noodoproepe"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-kaart, gleuf: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Toeganklikheid"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Raak weer om oop te maak"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 84e8521..d4bd2e2 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"የአደጋ ጥሪዎችን ብቻ ለማድረግ"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM ካርድ፣ ማስገቢያ ቀዳዳ፦ <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"ተደራሽነት"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"ለመክፈት ዳግም ይንኩ"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index d02308d..3fa5651 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -563,4 +563,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"مكالمات الطوارئ فقط"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"‏شريحة SIM، المنفذ: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"إمكانية الوصول"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"المس مرة أخرى للفتح"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 45b06e7..6d6cce0 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Само спешни обаждания"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM карта, слот: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Достъпност"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 2fdc608..662e0a9 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"শুধুমাত্র জরুরী কলিং"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM কার্ড, স্লট: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"অ্যাক্সেসযোগ্যতা"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"খোলার জন্য আবার স্পর্শ করুন"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 6c0384a..b894283 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Només trucades d\'emergència"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Targeta SIM, ranura: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accessibilitat"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index a1f5873..8c0e21b 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Pouze tísňová volání"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM karta, slot: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Usnadnění"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Otevřete opětovným klepnutím"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 16e3e26..4e6cdbd 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -85,9 +85,9 @@
     <string name="call_settings" msgid="6112441768261754562">"Indstillinger for opkald"</string>
     <string name="additional_gsm_call_settings" msgid="1391795981938800617">"Yderligere indstillinger"</string>
     <string name="additional_gsm_call_settings_with_label" msgid="1385241520708457376">"Yderligere indstillinger (<xliff:g id="SUBSCRIPTIONLABEL">%s</xliff:g>)"</string>
-    <string name="sum_gsm_call_settings" msgid="4076647190996778012">"Yderligere indstillinger for opkald er for kun GSM"</string>
+    <string name="sum_gsm_call_settings" msgid="4076647190996778012">"Yderligere indstillinger for GSM-opkald"</string>
     <string name="additional_cdma_call_settings" msgid="8628958775721886909">"Yderligere CDMA-opkaldsindstillinger"</string>
-    <string name="sum_cdma_call_settings" msgid="284753265979035549">"Yderligere indstillinger for opkald er for kun CDMA"</string>
+    <string name="sum_cdma_call_settings" msgid="284753265979035549">"Yderligere indstillinger for CDMA-opkald"</string>
     <string name="labelNwService" msgid="4699970172021870983">"Indstillinger for netværkstjeneste"</string>
     <string name="labelCallerId" msgid="3888899447379069198">"Opkalds-id"</string>
     <string name="sum_loading_settings" msgid="1826692909391168620">"Indlæser indstillinger…"</string>
@@ -557,4 +557,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Kun nødopkald"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-kortholder: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Hjælpefunktioner"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index e465fc3..ce96779 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -555,4 +555,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Nur Notrufe"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-Karte, Schacht: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Bedienungshilfen"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Zum Öffnen erneut berühren"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 1adf9c7..57aa315 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Μόνο κλήσεις έκτακτης ανάγκης"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Κάρτα SIM, υποδοχή: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Προσβασιμότητα"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Αγγίξτε ξανά για άνοιγμα"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index a585334..838f758 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Emergency calling only"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM card, slot: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accessibility"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Touch again to open"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index a585334..838f758 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Emergency calling only"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM card, slot: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accessibility"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Touch again to open"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index a585334..838f758 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Emergency calling only"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM card, slot: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accessibility"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Touch again to open"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index e5942bc..9337360 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Solo llamada de emergencia"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Tarjeta SIM, ranura: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accesibilidad"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Vuelve a tocar para realizar la acción."</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 063080b..7467ace 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -555,4 +555,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Solo llamadas de emergencia"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Tarjeta SIM, ranura: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accesibilidad"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Vuelve a tocar para abrir"</string>
 </resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index 95e981d..cbb59b0 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Ainult hädaabikõned"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-kaart, pilu: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Juurdepääsetavus"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index bfe9bcd..5089eed 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Larrialdi-deiak soilik"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM txartela, <xliff:g id="SLOT_ID">%s</xliff:g> erretena"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Erabilerraztasuna"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Irekitzeko, ukitu berriro"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index ff19a90..d0b5af7 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"فقط تماس‌های اضطراری"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"سیم‌کارت، شکاف: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"دسترس‌پذیری"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"لمس دوباره برای باز کردن"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 126edf6..c162b82 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -555,4 +555,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Vain hätäpuhelut"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-kortti, paikka: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Esteettömyys"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index a2c303d..2006eac 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -555,4 +555,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Appels d\'urgence seulement"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Carte SIM, fente : <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accessibilité"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Touchez à nouveau pour ouvrir"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 559e9bf..777b1e7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Appels d\'urgence seulement"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Carte SIM, emplacement : <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accessibilité"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Appuyer à nouveau pour ouvrir"</string>
 </resources>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 3118774..d8064c1 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Só chamadas de emerxencia"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Tarxeta SIM, rañura: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accesibilidade"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Toca outra vez para abrir"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 9f2341a..773874f 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -28,7 +28,7 @@
     <string name="mmiStarted" msgid="6347869857061147003">"MMI कोड प्रारंभ किया गया"</string>
     <string name="ussdRunning" msgid="485588686340541690">"USSD कोड चल रहा है…"</string>
     <string name="mmiCancelled" msgid="2771923949751842276">"MMI कोड रद्द किया गया"</string>
-    <string name="cancel" msgid="5044513931633602634">"रहने दें"</string>
+    <string name="cancel" msgid="5044513931633602634">"अभी नहीं"</string>
     <string name="enter_input" msgid="1810529547726803893">"USSD संदेश <xliff:g id="MIN_LEN">%d</xliff:g> और <xliff:g id="MAX_LEN">%d</xliff:g> वर्णों के बीच होना चाहिए. कृपया पुन: प्रयास करें."</string>
     <string name="manageConferenceLabel" msgid="4691922394301969053">"कॉन्फ़्रेंस कॉल प्रबंधित करें"</string>
     <string name="ok" msgid="3811371167865772377">"ठीक है"</string>
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"केवल आपातकालीन कॉल"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"सिम कार्ड, स्‍लॉट: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"सरल उपयोग"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"खोलने के लिए पुन: स्पर्श करें"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 9f8cbb7..765469d 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -554,4 +554,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Samo hitni pozivi"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM kartica, utor: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Pristupačnost"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Dodirnite ponovo da biste otvorili"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index e862ea3..69796ec 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Csak segélyhívás"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM kártya, bővítőhely: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Kisegítő lehetőségek"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Érintse meg ismét a megnyitáshoz"</string>
 </resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index 801b181..fb20898 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -557,4 +557,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Միայն արտակարգ իրավիճակների զանգեր"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM քարտ, բնիկը՝ <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Մատչելիություն"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 836f76a..fb886a5 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Hanya untuk panggilan darurat"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Aksesibilitas"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Sentuh lagi untuk membuka"</string>
 </resources>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 669c540..b4239bf 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Eingöngu neyðarsímtöl"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-kort, rauf: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Aðgengi"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Snertu aftur til að opna"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 14bfc94..fb90595 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -555,4 +555,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Solo chiamate di emergenza"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Scheda SIM, slot: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accessibilità"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Tocca di nuovo per aprire"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index f5ede0a..d4c5985 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"שיחות חירום בלבד"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"‏כרטיס SIM, חריץ: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"נגישות"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"גע שוב כדי לפתוח"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 10afbfa..ef350dd 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"緊急通報のみ"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIMカード、スロット: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"ユーザー補助機能"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"開くにはもう一度タップしてください"</string>
 </resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 9c77685..95090bf 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"მხოლოდ საგანგებო ნომრებზე დარეკვა"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM ბარათი, სლოტი: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"მარტივი წვდომა"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 4cbd8d5..3c8f800 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Тек жедел қоңыраулар"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM картасы, ұяшық: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Арнайы мүмкіндіктер"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index f5ae566..7663b1c 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"សម្រាប់ការហៅពេលអាសន្នប៉ុណ្ណោះ"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"ស៊ីមកាត រន្ធ៖ <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"លទ្ធភាពប្រើប្រាស់"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 1ffa586..795e63c 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"ತುರ್ತು ಕರೆ ಮಾಡುವಿಕೆ ಮಾತ್ರ"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM ಕಾರ್ಡ್, ಸ್ಲಾಟ್: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"ಪ್ರವೇಶಿಸುವಿಕೆ"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"ತೆರೆಯಲು ಮತ್ತೊಮ್ಮೆ ಸ್ಪರ್ಶಿಸಿ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index ae65bd4..a95549a 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"긴급 전화 전용"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM 카드, 슬롯: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"접근성"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index d2c57c0..a9c4cc0 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -832,4 +832,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Шашылыш чалуу гана"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-карта, оюкча: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Жеткиликтүүлүк"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index 1727bc4..68601ac 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"ການ​ໂທ​ສຸກ​ເສີນ​ເທົ່າ​ນັ້ນ"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"ແຜ່ນ SIM, ຊ່ອງ: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"​ການ​ຊ່ວຍ​ເຂົ້າ​ເຖິງ"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"​ແຕະ​ອີກ​ເທື່ອ​ນຶ່ງ​ເພື່ອ​ເປີດ"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 2091156..2e158c0 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Tik skambučiai pagalbos numeriu"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM kortelė, lizdas: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Pritaikymas neįgaliesiems"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Palieskite dar kartą, kad atidarytumėte"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 36904ff..b5b3f73 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -556,4 +556,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Tikai ārkārtas izsaukumi"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM karte, slots: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Pieejamība"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 2c9eb36..d12860f 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -557,4 +557,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Само итни повикувања"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"СИМ-картичка, отвор: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Пристапност"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 8f5470e..40953e9 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"അടിയന്തിര കോൾചെയ്യൽ മാത്രം"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM കാർഡ്, സ്ലോട്ട്: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"പ്രവേശനക്ഷമത"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"തുറക്കുന്നതിന് വീണ്ടും സ്‌പർശിക്കുക"</string>
 </resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index 80eea8c..0995280 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Зөвхөн яаралтай дуудлага"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM карт, оролт: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Хандалт"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Нээхийн тулд дахин хүрнэ үү"</string>
 </resources>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 4ed0cc8..70adcce 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"केवळ आणीबाणी कॉल करणे"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"सिम कार्ड, स्लॉट: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"प्रवेशयोग्यता"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"उघडण्यासाठी पुन्हा स्पर्श करा"</string>
 </resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index bfe8c87..e3dd28b 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Panggilan kecemasan sahaja"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Kad SIM, slot: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Kebolehaksesan"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 6a4f4f6..c3ba550 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"အရေးပေါ် ခေါ်ဆိုမှုသာလျှင်"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM ကဒ်၊ အပေါက်: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"ရယူသုံးနိုင်မှု"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"ဖွင့်ရန် ထပ်ပြီး ထိပါ"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index f1e2e4f..d3bfe6a 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Kun nødanrop"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-kort, spor: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Tilgjengelighet"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Trykk på nytt for å åpne"</string>
 </resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index e6538d3..efc996c 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"आपातकालीन कल मात्र"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM कार्ड, स्लट: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"पहुँचता"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"खोल्न फेरि छुनुहोस्"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 6ba9f29..b681d59 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Alleen noodoproepen"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Simkaart, sleuf: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Toegankelijkheid"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Tik nogmaals om te openen"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 748ef43..e69148d 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Tylko połączenia alarmowe"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Karta SIM, gniazdo: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Ułatwienia dostępu"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Kliknij ponownie, by otworzyć"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index a209b79..581332d 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Apenas chamadas de emergência"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Cartão SIM, ranhura: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Acessibilidade"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Tocar novamente para abrir"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 330f333..0d6f4fb 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Somente chamadas de emergência"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Cartão SIM, slot: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Acessibilidade"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Toque novamente para abrir"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index ff55e23..666077a 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -554,4 +554,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Numai apeluri de urgență"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Cardul SIM, slotul: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accesibilitate"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Atingeți din nou pentru a deschide"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 80823cd..8c3c7ef 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -559,4 +559,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Только экстренные вызовы"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-карта, разъем: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Специальные возможности"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 303f67c..a2f71e8 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"හදිස්සි ඇමතූම් පමණි"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM කාඩ්පත්, තව්ව: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"ප්‍රවේශ්‍යතාව"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"විවෘත කිරීමට නැවත ස්පර්ශ කරන්න"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 27aefdb..999800f 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Tiesňové volania iba na čísla"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Slot na SIM kartu: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Dostupnosť"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Otvorte opätovným klepnutím"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 170b88f..850a830 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Samo klicanje v sili"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Kartica SIM, reža: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Funkcije za ljudi s posebnimi potrebami"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Dotaknite se znova, če želite odpreti"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 8c8b540..d7ad595 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -554,4 +554,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Само за хитне позиве"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM картица, отвор: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Приступачност"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Додирните поново да бисте отворили"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 9137fcb..1a80762 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Endast nödsamtal"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-kortsplats: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Tillgänglighet"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Tryck igen för att öppna"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index bed5719..f144635 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Simu za dharula pekee"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM kadi, nafasi: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Zana za walio na matatizo ya kuona au kusikia"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Gusa tena ili ufungue"</string>
 </resources>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 7f67c60..44da834 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"அவசர அழைப்பு மட்டுமே"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"சிம் கார்டு, ஸ்லாட்: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"அணுகல் தன்மை"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"திறக்க, மீண்டும் தட்டவும்"</string>
 </resources>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index fefc336..7c178bd 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"అత్యవసర కాలింగ్ మాత్రమే"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM కార్డ్, స్లాట్: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"ప్రాప్యత సామర్థ్యం"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"తెరవడానికి మళ్లీ తాకండి"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index fa00f61..d002125 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"เฉพาะหมายเลขฉุกเฉินเท่านั้น"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"ซิมการ์ด ช่อง: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"การเข้าถึง"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index dfc5757..4eb4cc4 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Emergency na pagtawag lang"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM card, slot: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Pagiging Naa-access"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Pinduting muli upang buksan"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 56878c9..f45e4d4 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Yalnızca acil durum çağrısı"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM kart, yuva: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Erişilebilirlik"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 321da1f..114225d 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -557,4 +557,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Лише екстрені виклики"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM-карта, роз’єм: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Спеціальні можливості"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Торкніться знову, щоб відкрити"</string>
 </resources>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index 5609b36..0090fa8 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"صرف ہنگامی کالنگ"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"‏SIM کارڈ، سلاٹ: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Accessibility"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index c3d930a..daaf640 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Faqat favqulodda qo‘ng‘iroqlar"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM karta, teshik: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Maxsus imkoniyatlar"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 502ce61..5feeaed 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -551,4 +551,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Chỉ gọi điện khẩn cấp"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Thẻ SIM, rãnh: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Trợ năng"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Chạm lại để mở"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 44e6ce7..abbbfab 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -551,4 +551,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"只能拨打紧急呼救电话"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM 卡,插槽:<xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"无障碍功能"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 336e21a..c32b246 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -557,4 +557,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"只限緊急通話"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM 卡,插槽:<xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"無障礙功能"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 0364eb2..71350ab 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -557,4 +557,6 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"僅限撥打緊急電話"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"SIM 卡,插槽:<xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"協助工具"</string>
+    <!-- no translation found for emergency_action_launch_hint (5841511849007540970) -->
+    <skip />
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 7c2a9e0..8a6cbc9 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -555,4 +555,5 @@
     <string name="sim_description_emergency_calls" msgid="7535215397212301562">"Ikholi ephuthumayo kuphela"</string>
     <string name="sim_description_default" msgid="4778679519938775515">"Ikhadi le-SIM, isilothi: <xliff:g id="SLOT_ID">%s</xliff:g>"</string>
     <string name="accessibility_settings_activity_title" msgid="8562004288733103868">"Ukufinyeleleka"</string>
+    <string name="emergency_action_launch_hint" msgid="5841511849007540970">"Thinta futhi ukuze uvule"</string>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 45b984e..67572ed 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -58,4 +58,5 @@
     <color name="network_operators_color_primary">#ff263238</color>
     <color name="network_operators_color_primary_dark">#ff21272b</color>
 
+    <color name="emergency_dialer_background">#ff263238</color>
 </resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
new file mode 100644
index 0000000..82a6add
--- /dev/null
+++ b/res/values/ids.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+    <item type="id" name="tag_intent" />
+</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 22732ef..4aa3922 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1292,4 +1292,7 @@
     <string name="tty_mode_key">button_tty_mode_key</string>
     <!-- DO NOT TRANSLATE. Internal key for a voicemail notification preference. -->
     <string name="wifi_calling_settings_key">button_wifi_calling_settings_key</string>
+
+    <!-- Hint appearing below a selected action on the emergency dialer telling user to tap again to execute the action [CHAR LIMIT=NONE] -->
+    <string name="emergency_action_launch_hint">Touch again to open</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 3d1b1ad..3c192cc 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -256,11 +256,12 @@
         <item name="android:src">@drawable/overflow_menu</item>
     </style>
 
-    <style name="EmergencyDialerTheme" parent="@android:style/Theme.Material.Light">
-        <item name="dialpad_key_button_touch_tint">@color/dialer_dialpad_touch_tint</item>
-        <item name="android:actionBarStyle">@style/TelephonyActionBarStyle</item>
-        <item name="android:colorPrimary">@color/dialer_theme_color</item>
-        <item name="android:colorPrimaryDark">@color/dialer_theme_color_dark</item>
+    <style name="EmergencyDialerTheme" parent="@android:style/Theme.Material.NoActionBar">
+        <item name="android:colorPrimary">@color/emergency_dialer_background</item>
+        <item name="android:colorPrimaryDark">@color/emergency_dialer_background</item>
+        <item name="android:windowBackground">@color/emergency_dialer_background</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:navigationBarColor">@android:color/transparent</item>
     </style>
 
     <style name="SimImportTheme" parent="@android:style/Theme.Material.Light">
diff --git a/sip/res/values-hi/strings.xml b/sip/res/values-hi/strings.xml
index 6894f9b..fbf3fd2 100644
--- a/sip/res/values-hi/strings.xml
+++ b/sip/res/values-hi/strings.xml
@@ -27,7 +27,7 @@
     <string name="sip_call_options_entry_2" msgid="1815335903940609729">"केवल SIP कॉल के लिए"</string>
     <string name="sip_call_options_wifi_only_entry_1" msgid="1358513095551847314">"सभी कॉल के लिए"</string>
     <string name="add_sip_account" msgid="800843669753980091">"खाता जोड़ें"</string>
-    <string name="remove_sip_account" msgid="1367664438506503690">"खाता निकालें"</string>
+    <string name="remove_sip_account" msgid="1367664438506503690">"खाता हटाएं"</string>
     <string name="sip_account_list" msgid="5610858485304821480">"SIP खाते"</string>
     <string name="saving_account" msgid="5336529880235177448">"खाता सहेज रहा है..."</string>
     <string name="removing_account" msgid="5537351356808985756">"खाता निकाल रहा है..."</string>
diff --git a/sip/res/values-kn-rIN/strings.xml b/sip/res/values-kn-rIN/strings.xml
index 01008c5..0e89dd2 100644
--- a/sip/res/values-kn-rIN/strings.xml
+++ b/sip/res/values-kn-rIN/strings.xml
@@ -56,7 +56,7 @@
     <string name="display_name_title" msgid="579241787583079773">"ಹೆಸರು ಪ್ರದರ್ಶಿಸು"</string>
     <string name="proxy_address_title" msgid="6890163365640631841">"ಹೊರಹೋಗುವ ಪ್ರಾಕ್ಸಿ ವಿಳಾಸ"</string>
     <string name="port_title" msgid="6693965912656593862">"ಪೋರ್ಟ್ ಸಂಖ್ಯೆ"</string>
-    <string name="transport_title" msgid="889155457465372527">"ಸಾಗಣೆ ಪ್ರಕಾರ"</string>
+    <string name="transport_title" msgid="889155457465372527">"ಸಾರಿಗೆ ಪ್ರಕಾರ"</string>
     <string name="send_keepalive_title" msgid="599627072150501159">"ಜೀವಂತವಾಗಿ ಇರಿಸು ಅನ್ನು ಕಳುಹಿಸಿ"</string>
     <string name="advanced_settings" msgid="6622996380747040711">"ಐಚ್ಛಿಕ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="auth_username_title" msgid="8262491689004708265">"ದೃಢೀಕರಣ ಬಳಕೆದಾರಹೆಸರು"</string>
diff --git a/src/com/android/phone/EmergencyActionGroup.java b/src/com/android/phone/EmergencyActionGroup.java
new file mode 100644
index 0000000..96adc5d
--- /dev/null
+++ b/src/com/android/phone/EmergencyActionGroup.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.List;
+
+public class EmergencyActionGroup extends FrameLayout implements View.OnClickListener {
+
+    private static final long HIDE_DELAY = 3000;
+    private static final int RIPPLE_DURATION = 600;
+    private static final long RIPPLE_PAUSE = 1000;
+
+    private final Interpolator mFastOutLinearInInterpolator;
+
+    private ViewGroup mSelectedContainer;
+    private TextView mSelectedLabel;
+    private View mRippleView;
+    private View mLaunchHint;
+
+    private View mLastRevealed;
+
+    public EmergencyActionGroup(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
+                android.R.interpolator.fast_out_linear_in);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        setupAssistActions();
+
+        mSelectedContainer = (ViewGroup) findViewById(R.id.selected_container);
+        mSelectedContainer.setOnClickListener(this);
+        mSelectedLabel = (TextView) findViewById(R.id.selected_label);
+        mRippleView = findViewById(R.id.ripple_view);
+        mLaunchHint = findViewById(R.id.launch_hint);
+    }
+
+    private void setupAssistActions() {
+        int[] buttonIds = new int[] {R.id.action1, R.id.action2, R.id.action3};
+
+        List<ResolveInfo> infos = resolveAssistPackageAndQueryActivites();
+
+        for (int i = 0; i < 3; i++) {
+            Button button = (Button) findViewById(buttonIds[i]);
+            boolean visible = false;
+
+            button.setOnClickListener(this);
+
+            if (infos != null && infos.size() > i && infos.get(i) != null) {
+                ResolveInfo info = infos.get(i);
+                ComponentName name = getComponentName(info);
+
+                button.setTag(R.id.tag_intent,
+                        new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE)
+                                .setComponent(name));
+                button.setText(info.loadLabel(getContext().getPackageManager()));
+                visible = true;
+            }
+
+            button.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    private List<ResolveInfo> resolveAssistPackageAndQueryActivites() {
+        List<ResolveInfo> infos = queryAssistActivities();
+
+        if (infos == null || infos.isEmpty()) {
+            PackageManager packageManager = getContext().getPackageManager();
+            Intent queryIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
+            infos = packageManager.queryIntentActivities(queryIntent, 0);
+
+            PackageInfo bestMatch = null;
+            for (int i = 0; i < infos.size(); i++) {
+                if (infos.get(i).activityInfo == null) continue;
+                String packageName = infos.get(i).activityInfo.packageName;
+                PackageInfo packageInfo;
+                try {
+                    packageInfo = packageManager.getPackageInfo(packageName, 0);
+                } catch (PackageManager.NameNotFoundException e) {
+                    continue;
+                }
+                // Get earliest installed app, but prioritize system apps.
+                if (bestMatch == null
+                        || !isSystemApp(bestMatch) && isSystemApp(packageInfo)
+                        || isSystemApp(bestMatch) == isSystemApp(packageInfo)
+                                && bestMatch.firstInstallTime > packageInfo.firstInstallTime) {
+                    bestMatch = packageInfo;
+                }
+            }
+
+            if (bestMatch != null) {
+                Settings.Secure.putString(getContext().getContentResolver(),
+                        Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+                        bestMatch.packageName);
+                return queryAssistActivities();
+            } else {
+                return null;
+            }
+        } else {
+            return infos;
+        }
+    }
+
+    private List<ResolveInfo> queryAssistActivities() {
+        String assistPackage = Settings.Secure.getString(
+                getContext().getContentResolver(),
+                Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
+        List<ResolveInfo> infos = null;
+
+        if (!TextUtils.isEmpty(assistPackage)) {
+            Intent queryIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE)
+                    .setPackage(assistPackage);
+            infos = getContext().getPackageManager().queryIntentActivities(queryIntent, 0);
+        }
+        return infos;
+    }
+
+    private boolean isSystemApp(PackageInfo info) {
+        return info.applicationInfo != null
+                && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    private ComponentName getComponentName(ResolveInfo resolveInfo) {
+        if (resolveInfo == null || resolveInfo.activityInfo == null) return null;
+        return new ComponentName(resolveInfo.activityInfo.packageName,
+                resolveInfo.activityInfo.name);
+    }
+
+    @Override
+    public void onClick(View v) {
+        switch (v.getId()) {
+            case R.id.action1:
+            case R.id.action2:
+            case R.id.action3:
+                revealTheButton(v);
+                break;
+            case R.id.selected_container:
+                getContext().startActivity((Intent) v.getTag(R.id.tag_intent));
+                break;
+        }
+    }
+
+    private void revealTheButton(View v) {
+        mSelectedContainer.setVisibility(VISIBLE);
+        int centerX = v.getLeft() + v.getWidth() / 2;
+        int centerY = v.getTop() + v.getHeight() / 2;
+        Animator reveal = ViewAnimationUtils.createCircularReveal(
+                mSelectedContainer,
+                centerX,
+                centerY,
+                0,
+                Math.max(centerX, mSelectedContainer.getWidth() - centerX)
+                        + Math.max(centerY, mSelectedContainer.getHeight() - centerY));
+        reveal.start();
+
+        animateHintText(mSelectedLabel, v, reveal);
+        animateHintText(mLaunchHint, v, reveal);
+
+        mSelectedLabel.setText(((Button) v).getText());
+        mSelectedContainer.setTag(R.id.tag_intent, v.getTag(R.id.tag_intent));
+        mLastRevealed = v;
+        mSelectedContainer.postDelayed(mHideRunnable, HIDE_DELAY);
+        mSelectedContainer.postDelayed(mRippleRunnable, RIPPLE_PAUSE / 2);
+    }
+
+    private void animateHintText(View selectedView, View v, Animator reveal) {
+        selectedView.setTranslationX(
+                (v.getLeft() + v.getWidth() / 2 - mSelectedContainer.getWidth() / 2) / 5);
+        selectedView.animate()
+                .setDuration(reveal.getDuration() / 3)
+                .setStartDelay(reveal.getDuration() / 5)
+                .translationX(0)
+                .setInterpolator(mFastOutLinearInInterpolator)
+                .start();
+    }
+
+    private void hideTheButton() {
+        View v = mLastRevealed;
+        int centerX = v.getLeft() + v.getWidth() / 2;
+        int centerY = v.getTop() + v.getHeight() / 2;
+        Animator reveal = ViewAnimationUtils.createCircularReveal(
+                mSelectedContainer,
+                centerX,
+                centerY,
+                Math.max(centerX, mSelectedContainer.getWidth() - centerX)
+                        + Math.max(centerY, mSelectedContainer.getHeight() - centerY),
+                0);
+        reveal.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mSelectedContainer.setVisibility(INVISIBLE);
+                mSelectedContainer.removeCallbacks(mRippleRunnable);
+            }
+        });
+        reveal.start();
+    }
+
+    private void startRipple() {
+        final View ripple = mRippleView;
+        ripple.animate().cancel();
+        ripple.setVisibility(VISIBLE);
+        Animator reveal = ViewAnimationUtils.createCircularReveal(
+                ripple,
+                ripple.getLeft() + ripple.getWidth() / 2,
+                ripple.getTop() + ripple.getHeight() / 2,
+                0,
+                ripple.getWidth() / 2);
+        reveal.setDuration(RIPPLE_DURATION);
+        reveal.start();
+
+        ripple.setAlpha(0);
+        ripple.animate().alpha(1).setDuration(RIPPLE_DURATION / 2)
+                .withEndAction(new Runnable() {
+            @Override
+            public void run() {
+                ripple.animate().alpha(0).setDuration(RIPPLE_DURATION / 2)
+                        .withEndAction(new Runnable() {
+                            @Override
+                            public void run() {
+                                ripple.setVisibility(INVISIBLE);
+                                postDelayed(mRippleRunnable, RIPPLE_PAUSE);
+                            }
+                        }).start();
+            }
+        }).start();
+    }
+
+    private final Runnable mHideRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (!isAttachedToWindow()) return;
+            hideTheButton();
+        }
+    };
+
+    private final Runnable mRippleRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (!isAttachedToWindow()) return;
+            startRipple();
+        }
+    };
+
+
+}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 64a2d46..59eb996 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -122,6 +122,8 @@
     private static final int EVENT_EXCHANGE_SIM_IO_DONE = 32;
     private static final int CMD_SET_VOICEMAIL_NUMBER = 33;
     private static final int EVENT_SET_VOICEMAIL_NUMBER_DONE = 34;
+    private static final int CMD_SET_NETWORK_SELECTION_MODE_AUTOMATIC = 35;
+    private static final int EVENT_SET_NETWORK_SELECTION_MODE_AUTOMATIC_DONE = 36;
 
     /** The singleton instance. */
     private static PhoneInterfaceManager sInstance;
@@ -555,7 +557,7 @@
                 case CMD_GET_PREFERRED_NETWORK_TYPE:
                     request = (MainThreadRequest) msg.obj;
                     onCompleted = obtainMessage(EVENT_GET_PREFERRED_NETWORK_TYPE_DONE, request);
-                    mPhone.getPreferredNetworkType(onCompleted);
+                    getPhoneFromRequest(request).getPreferredNetworkType(onCompleted);
                     break;
 
                 case EVENT_GET_PREFERRED_NETWORK_TYPE_DONE:
@@ -583,7 +585,7 @@
                     request = (MainThreadRequest) msg.obj;
                     onCompleted = obtainMessage(EVENT_SET_PREFERRED_NETWORK_TYPE_DONE, request);
                     int networkType = (Integer) request.argument;
-                    mPhone.setPreferredNetworkType(networkType, onCompleted);
+                    getPhoneFromRequest(request).setPreferredNetworkType(networkType, onCompleted);
                     break;
 
                 case EVENT_SET_PREFERRED_NETWORK_TYPE_DONE:
@@ -617,6 +619,17 @@
                     handleNullReturnEvent(msg, "setVoicemailNumber");
                     break;
 
+                case CMD_SET_NETWORK_SELECTION_MODE_AUTOMATIC:
+                    request = (MainThreadRequest) msg.obj;
+                    onCompleted = obtainMessage(EVENT_SET_NETWORK_SELECTION_MODE_AUTOMATIC_DONE,
+                            request);
+                    getPhoneFromRequest(request).setNetworkSelectionModeAutomatic(onCompleted);
+                    break;
+
+                case EVENT_SET_NETWORK_SELECTION_MODE_AUTOMATIC_DONE:
+                    handleNullReturnEvent(msg, "setNetworkSelectionModeAutomatic");
+                    break;
+
                 default:
                     Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
                     break;
@@ -1860,6 +1873,17 @@
     }
 
     /**
+     * Set the network selection mode to automatic.
+     *
+     */
+    @Override
+    public void setNetworkSelectionModeAutomatic(int subId) {
+        enforceModifyPermissionOrCarrierPrivilege();
+        if (DBG) log("setNetworkSelectionModeAutomatic: subId " + subId);
+        sendRequest(CMD_SET_NETWORK_SELECTION_MODE_AUTOMATIC, null, subId);
+    }
+
+    /**
      * Get the calculated preferred network type.
      * Used for debugging incorrect network type.
      *
@@ -1878,10 +1902,10 @@
      * @return the preferred network type, defined in RILConstants.java.
      */
     @Override
-    public int getPreferredNetworkType() {
+    public int getPreferredNetworkType(int subId) {
         enforceModifyPermissionOrCarrierPrivilege();
         if (DBG) log("getPreferredNetworkType");
-        int[] result = (int[]) sendRequest(CMD_GET_PREFERRED_NETWORK_TYPE, null);
+        int[] result = (int[]) sendRequest(CMD_GET_PREFERRED_NETWORK_TYPE, null, subId);
         int networkType = (result != null ? result[0] : -1);
         if (DBG) log("getPreferredNetworkType: " + networkType);
         return networkType;
@@ -1895,15 +1919,14 @@
      * @return true on success; false on any failure.
      */
     @Override
-    public boolean setPreferredNetworkType(int networkType) {
+    public boolean setPreferredNetworkType(int subId, int networkType) {
         enforceModifyPermissionOrCarrierPrivilege();
-        final int phoneSubId = mPhone.getSubId();
-        if (DBG) log("setPreferredNetworkType: type " + networkType);
-        Boolean success = (Boolean) sendRequest(CMD_SET_PREFERRED_NETWORK_TYPE, networkType);
+        if (DBG) log("setPreferredNetworkType: subId " + subId + " type " + networkType);
+        Boolean success = (Boolean) sendRequest(CMD_SET_PREFERRED_NETWORK_TYPE, networkType, subId);
         if (DBG) log("setPreferredNetworkType: " + (success ? "ok" : "fail"));
         if (success) {
             Settings.Global.putInt(mPhone.getContext().getContentResolver(),
-                    Settings.Global.PREFERRED_NETWORK_MODE + phoneSubId, networkType);
+                    Settings.Global.PREFERRED_NETWORK_MODE + subId, networkType);
         }
         return success;
     }
diff --git a/src/com/android/phone/common/mail/utils/LogUtils.java b/src/com/android/phone/common/mail/utils/LogUtils.java
new file mode 100644
index 0000000..b45c84d
--- /dev/null
+++ b/src/com/android/phone/common/mail/utils/LogUtils.java
@@ -0,0 +1,416 @@
+/**
+ * Copyright (c) 2015, Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.phone.common.mail.utils;
+
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class LogUtils {
+    public static final String TAG = "Email Log";
+
+    // "GMT" + "+" or "-" + 4 digits
+    private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE =
+            Pattern.compile("GMT([-+]\\d{4})$");
+
+    private static final String ACCOUNT_PREFIX = "account:";
+
+    /**
+     * Priority constant for the println method; use LogUtils.v.
+     */
+    public static final int VERBOSE = Log.VERBOSE;
+
+    /**
+     * Priority constant for the println method; use LogUtils.d.
+     */
+    public static final int DEBUG = Log.DEBUG;
+
+    /**
+     * Priority constant for the println method; use LogUtils.i.
+     */
+    public static final int INFO = Log.INFO;
+
+    /**
+     * Priority constant for the println method; use LogUtils.w.
+     */
+    public static final int WARN = Log.WARN;
+
+    /**
+     * Priority constant for the println method; use LogUtils.e.
+     */
+    public static final int ERROR = Log.ERROR;
+
+    /**
+     * Used to enable/disable logging that we don't want included in
+     * production releases.  This should be set to DEBUG for production releases, and VERBOSE for
+     * internal builds.
+     */
+    // STOPSHIP: ship with DEBUG set
+    private static final int MAX_ENABLED_LOG_LEVEL = VERBOSE;
+
+    private static Boolean sDebugLoggingEnabledForTests = null;
+
+    /**
+     * Enable debug logging for unit tests.
+     */
+    @VisibleForTesting
+    public static void setDebugLoggingEnabledForTests(boolean enabled) {
+        setDebugLoggingEnabledForTestsInternal(enabled);
+    }
+
+    protected static void setDebugLoggingEnabledForTestsInternal(boolean enabled) {
+        sDebugLoggingEnabledForTests = Boolean.valueOf(enabled);
+    }
+
+    /**
+     * Returns true if the build configuration prevents debug logging.
+     */
+    @VisibleForTesting
+    public static boolean buildPreventsDebugLogging() {
+        return MAX_ENABLED_LOG_LEVEL > VERBOSE;
+    }
+
+    /**
+     * Returns a boolean indicating whether debug logging is enabled.
+     */
+    protected static boolean isDebugLoggingEnabled(String tag) {
+        if (buildPreventsDebugLogging()) {
+            return false;
+        }
+        if (sDebugLoggingEnabledForTests != null) {
+            return sDebugLoggingEnabledForTests.booleanValue();
+        }
+        return Log.isLoggable(tag, Log.DEBUG) || Log.isLoggable(TAG, Log.DEBUG);
+    }
+
+    /**
+     * Returns a String for the specified content provider uri.  This will do
+     * sanitation of the uri to remove PII if debug logging is not enabled.
+     */
+    public static String contentUriToString(final Uri uri) {
+        return contentUriToString(TAG, uri);
+    }
+
+    /**
+     * Returns a String for the specified content provider uri.  This will do
+     * sanitation of the uri to remove PII if debug logging is not enabled.
+     */
+    public static String contentUriToString(String tag, Uri uri) {
+        if (isDebugLoggingEnabled(tag)) {
+            // Debug logging has been enabled, so log the uri as is
+            return uri.toString();
+        } else {
+            // Debug logging is not enabled, we want to remove the email address from the uri.
+            List<String> pathSegments = uri.getPathSegments();
+
+            Uri.Builder builder = new Uri.Builder()
+                    .scheme(uri.getScheme())
+                    .authority(uri.getAuthority())
+                    .query(uri.getQuery())
+                    .fragment(uri.getFragment());
+
+            // This assumes that the first path segment is the account
+            final String account = pathSegments.get(0);
+
+            builder = builder.appendPath(sanitizeAccountName(account));
+            for (int i = 1; i < pathSegments.size(); i++) {
+                builder.appendPath(pathSegments.get(i));
+            }
+            return builder.toString();
+        }
+    }
+
+    /**
+     * Sanitizes an account name.  If debug logging is not enabled, a sanitized name
+     * is returned.
+     */
+    public static String sanitizeAccountName(String accountName) {
+        if (TextUtils.isEmpty(accountName)) {
+            return "";
+        }
+
+        return ACCOUNT_PREFIX + sanitizeName(TAG, accountName);
+    }
+
+    public static String sanitizeName(final String tag, final String name) {
+        if (TextUtils.isEmpty(name)) {
+            return "";
+        }
+
+        if (isDebugLoggingEnabled(tag)) {
+            return name;
+        }
+
+        return String.valueOf(name.hashCode());
+    }
+
+    /**
+     * Checks to see whether or not a log for the specified tag is loggable at the specified level.
+     */
+    public static boolean isLoggable(String tag, int level) {
+        if (MAX_ENABLED_LOG_LEVEL > level) {
+            return false;
+        }
+        return Log.isLoggable(tag, level) || Log.isLoggable(TAG, level);
+    }
+
+    /**
+     * Send a {@link #VERBOSE} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int v(String tag, String format, Object... args) {
+        if (isLoggable(tag, VERBOSE)) {
+            return Log.v(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #VERBOSE} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int v(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, VERBOSE)) {
+            return Log.v(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #DEBUG} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int d(String tag, String format, Object... args) {
+        if (isLoggable(tag, DEBUG)) {
+            return Log.d(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #DEBUG} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int d(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, DEBUG)) {
+            return Log.d(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #INFO} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int i(String tag, String format, Object... args) {
+        if (isLoggable(tag, INFO)) {
+            return Log.i(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #INFO} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int i(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, INFO)) {
+            return Log.i(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #WARN} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int w(String tag, String format, Object... args) {
+        if (isLoggable(tag, WARN)) {
+            return Log.w(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #WARN} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int w(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, WARN)) {
+            return Log.w(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #ERROR} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int e(String tag, String format, Object... args) {
+        if (isLoggable(tag, ERROR)) {
+            return Log.e(tag, String.format(format, args));
+        }
+        return 0;
+    }
+
+    /**
+     * Send a {@link #ERROR} log message.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int e(String tag, Throwable tr, String format, Object... args) {
+        if (isLoggable(tag, ERROR)) {
+            return Log.e(tag, String.format(format, args), tr);
+        }
+        return 0;
+    }
+
+    /**
+     * What a Terrible Failure: Report a condition that should never happen.
+     * The error will always be logged at level ASSERT with the call stack.
+     * Depending on system configuration, a report may be added to the
+     * {@link android.os.DropBoxManager} and/or the process may be terminated
+     * immediately with an error dialog.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int wtf(String tag, String format, Object... args) {
+        return Log.wtf(tag, String.format(format, args), new Error());
+    }
+
+    /**
+     * What a Terrible Failure: Report a condition that should never happen.
+     * The error will always be logged at level ASSERT with the call stack.
+     * Depending on system configuration, a report may be added to the
+     * {@link android.os.DropBoxManager} and/or the process may be terminated
+     * immediately with an error dialog.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param tr An exception to log
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     */
+    public static int wtf(String tag, Throwable tr, String format, Object... args) {
+        return Log.wtf(tag, String.format(format, args), tr);
+    }
+
+
+    /**
+     * Try to make a date MIME(RFC 2822/5322)-compliant.
+     *
+     * It fixes:
+     * - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700"
+     *   (4 digit zone value can't be preceded by "GMT")
+     *   We got a report saying eBay sends a date in this format
+     */
+    public static String cleanUpMimeDate(String date) {
+        if (TextUtils.isEmpty(date)) {
+            return date;
+        }
+        date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1");
+        return date;
+    }
+
+
+    public static String byteToHex(int b) {
+        return byteToHex(new StringBuilder(), b).toString();
+    }
+
+    public static StringBuilder byteToHex(StringBuilder sb, int b) {
+        b &= 0xFF;
+        sb.append("0123456789ABCDEF".charAt(b >> 4));
+        sb.append("0123456789ABCDEF".charAt(b & 0xF));
+        return sb;
+    }
+
+}
\ No newline at end of file
diff --git a/src/org/apache/commons/io/IOUtils.java b/src/org/apache/commons/io/IOUtils.java
new file mode 100644
index 0000000..b414507
--- /dev/null
+++ b/src/org/apache/commons/io/IOUtils.java
@@ -0,0 +1,1202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.io;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * General IO stream manipulation utilities.
+ * <p>
+ * This class provides static utility methods for input/output operations.
+ * <ul>
+ * <li>closeQuietly - these methods close a stream ignoring nulls and exceptions
+ * <li>toXxx/read - these methods read data from a stream
+ * <li>write - these methods write data to a stream
+ * <li>copy - these methods copy all the data from one stream to another
+ * <li>contentEquals - these methods compare the content of two streams
+ * </ul>
+ * <p>
+ * The byte-to-char methods and char-to-byte methods involve a conversion step.
+ * Two methods are provided in each case, one that uses the platform default
+ * encoding and the other which allows you to specify an encoding. You are
+ * encouraged to always specify an encoding because relying on the platform
+ * default can lead to unexpected results, for example when moving from
+ * development to production.
+ * <p>
+ * All the methods in this class that read a stream are buffered internally.
+ * This means that there is no cause to use a <code>BufferedInputStream</code>
+ * or <code>BufferedReader</code>. The default buffer size of 4K has been shown
+ * to be efficient in tests.
+ * <p>
+ * Wherever possible, the methods in this class do <em>not</em> flush or close
+ * the stream. This is to avoid making non-portable assumptions about the
+ * streams' origin and further use. Thus the caller is still responsible for
+ * closing streams after use.
+ * <p>
+ * Origin of code: Excalibur.
+ *
+ * @author Peter Donald
+ * @author Jeff Turner
+ * @author Matthew Hawthorne
+ * @author Stephen Colebourne
+ * @author Gareth Davis
+ * @author Ian Springer
+ * @author Niall Pemberton
+ * @author Sandy McArthur
+ * @version $Id: IOUtils.java 481854 2006-12-03 18:30:07Z scolebourne $
+ */
+public class IOUtils {
+    // NOTE: This class is focussed on InputStream, OutputStream, Reader and
+    // Writer. Each method should take at least one of these as a parameter,
+    // or return one of them.
+
+    /**
+     * The Unix directory separator character.
+     */
+    public static final char DIR_SEPARATOR_UNIX = '/';
+    /**
+     * The Windows directory separator character.
+     */
+    public static final char DIR_SEPARATOR_WINDOWS = '\\';
+    /**
+     * The system directory separator character.
+     */
+    public static final char DIR_SEPARATOR = File.separatorChar;
+    /**
+     * The Unix line separator string.
+     */
+    public static final String LINE_SEPARATOR_UNIX = "\n";
+    /**
+     * The Windows line separator string.
+     */
+    public static final String LINE_SEPARATOR_WINDOWS = "\r\n";
+    /**
+     * The system line separator string.
+     */
+    public static final String LINE_SEPARATOR;
+    static {
+        // avoid security issues
+        StringWriter buf = new StringWriter(4);
+        PrintWriter out = new PrintWriter(buf);
+        out.println();
+        LINE_SEPARATOR = buf.toString();
+    }
+
+    /**
+     * The default buffer size to use.
+     */
+    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+    /**
+     * Instances should NOT be constructed in standard programming.
+     */
+    public IOUtils() {
+        super();
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Unconditionally close an <code>Reader</code>.
+     * <p>
+     * Equivalent to {@link Reader#close()}, except any exceptions will be ignored.
+     * This is typically used in finally blocks.
+     *
+     * @param input  the Reader to close, may be null or already closed
+     */
+    public static void closeQuietly(Reader input) {
+        try {
+            if (input != null) {
+                input.close();
+            }
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+
+    /**
+     * Unconditionally close a <code>Writer</code>.
+     * <p>
+     * Equivalent to {@link Writer#close()}, except any exceptions will be ignored.
+     * This is typically used in finally blocks.
+     *
+     * @param output  the Writer to close, may be null or already closed
+     */
+    public static void closeQuietly(Writer output) {
+        try {
+            if (output != null) {
+                output.close();
+            }
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+
+    /**
+     * Unconditionally close an <code>InputStream</code>.
+     * <p>
+     * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.
+     * This is typically used in finally blocks.
+     *
+     * @param input  the InputStream to close, may be null or already closed
+     */
+    public static void closeQuietly(InputStream input) {
+        try {
+            if (input != null) {
+                input.close();
+            }
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+
+    /**
+     * Unconditionally close an <code>OutputStream</code>.
+     * <p>
+     * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored.
+     * This is typically used in finally blocks.
+     *
+     * @param output  the OutputStream to close, may be null or already closed
+     */
+    public static void closeQuietly(OutputStream output) {
+        try {
+            if (output != null) {
+                output.close();
+            }
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+
+    // read toByteArray
+    //-----------------------------------------------------------------------
+    /**
+     * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @return the requested byte array
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     */
+    public static byte[] toByteArray(InputStream input) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        copy(input, output);
+        return output.toByteArray();
+    }
+
+    /**
+     * Get the contents of a <code>Reader</code> as a <code>byte[]</code>
+     * using the default character encoding of the platform.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @return the requested byte array
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     */
+    public static byte[] toByteArray(Reader input) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        copy(input, output);
+        return output.toByteArray();
+    }
+
+    /**
+     * Get the contents of a <code>Reader</code> as a <code>byte[]</code>
+     * using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @param encoding  the encoding to use, null means platform default
+     * @return the requested byte array
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static byte[] toByteArray(Reader input, String encoding)
+            throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        copy(input, output, encoding);
+        return output.toByteArray();
+    }
+
+    /**
+     * Get the contents of a <code>String</code> as a <code>byte[]</code>
+     * using the default character encoding of the platform.
+     * <p>
+     * This is the same as {@link String#getBytes()}.
+     *
+     * @param input  the <code>String</code> to convert
+     * @return the requested byte array
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs (never occurs)
+     * @deprecated Use {@link String#getBytes()}
+     */
+    @Deprecated
+    public static byte[] toByteArray(String input) throws IOException {
+        return input.getBytes();
+    }
+
+    // read char[]
+    //-----------------------------------------------------------------------
+    /**
+     * Get the contents of an <code>InputStream</code> as a character array
+     * using the default character encoding of the platform.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param is  the <code>InputStream</code> to read from
+     * @return the requested character array
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static char[] toCharArray(InputStream is) throws IOException {
+        CharArrayWriter output = new CharArrayWriter();
+        copy(is, output);
+        return output.toCharArray();
+    }
+
+    /**
+     * Get the contents of an <code>InputStream</code> as a character array
+     * using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param is  the <code>InputStream</code> to read from
+     * @param encoding  the encoding to use, null means platform default
+     * @return the requested character array
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static char[] toCharArray(InputStream is, String encoding)
+            throws IOException {
+        CharArrayWriter output = new CharArrayWriter();
+        copy(is, output, encoding);
+        return output.toCharArray();
+    }
+
+    /**
+     * Get the contents of a <code>Reader</code> as a character array.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @return the requested character array
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static char[] toCharArray(Reader input) throws IOException {
+        CharArrayWriter sw = new CharArrayWriter();
+        copy(input, sw);
+        return sw.toCharArray();
+    }
+
+    // read toString
+    //-----------------------------------------------------------------------
+    /**
+     * Get the contents of an <code>InputStream</code> as a String
+     * using the default character encoding of the platform.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @return the requested String
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     */
+    public static String toString(InputStream input) throws IOException {
+        StringWriter sw = new StringWriter();
+        copy(input, sw);
+        return sw.toString();
+    }
+
+    /**
+     * Get the contents of an <code>InputStream</code> as a String
+     * using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @param encoding  the encoding to use, null means platform default
+     * @return the requested String
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     */
+    public static String toString(InputStream input, String encoding)
+            throws IOException {
+        StringWriter sw = new StringWriter();
+        copy(input, sw, encoding);
+        return sw.toString();
+    }
+
+    /**
+     * Get the contents of a <code>Reader</code> as a String.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @return the requested String
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     */
+    public static String toString(Reader input) throws IOException {
+        StringWriter sw = new StringWriter();
+        copy(input, sw);
+        return sw.toString();
+    }
+
+    /**
+     * Get the contents of a <code>byte[]</code> as a String
+     * using the default character encoding of the platform.
+     *
+     * @param input the byte array to read from
+     * @return the requested String
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs (never occurs)
+     * @deprecated Use {@link String#String(byte[])}
+     */
+    @Deprecated
+    public static String toString(byte[] input) throws IOException {
+        return new String(input);
+    }
+
+    /**
+     * Get the contents of a <code>byte[]</code> as a String
+     * using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     *
+     * @param input the byte array to read from
+     * @param encoding  the encoding to use, null means platform default
+     * @return the requested String
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs (never occurs)
+     * @deprecated Use {@link String#String(byte[],String)}
+     */
+    @Deprecated
+    public static String toString(byte[] input, String encoding)
+            throws IOException {
+        if (encoding == null) {
+            return new String(input);
+        } else {
+            return new String(input, encoding);
+        }
+    }
+
+    // readLines
+    //-----------------------------------------------------------------------
+    /**
+     * Get the contents of an <code>InputStream</code> as a list of Strings,
+     * one entry per line, using the default character encoding of the platform.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input  the <code>InputStream</code> to read from, not null
+     * @return the list of Strings, never null
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static List<String> readLines(InputStream input) throws IOException {
+        InputStreamReader reader = new InputStreamReader(input);
+        return readLines(reader);
+    }
+
+    /**
+     * Get the contents of an <code>InputStream</code> as a list of Strings,
+     * one entry per line, using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input  the <code>InputStream</code> to read from, not null
+     * @param encoding  the encoding to use, null means platform default
+     * @return the list of Strings, never null
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static List<String> readLines(InputStream input, String encoding) throws IOException {
+        if (encoding == null) {
+            return readLines(input);
+        } else {
+            InputStreamReader reader = new InputStreamReader(input, encoding);
+            return readLines(reader);
+        }
+    }
+
+    /**
+     * Get the contents of a <code>Reader</code> as a list of Strings,
+     * one entry per line.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     *
+     * @param input  the <code>Reader</code> to read from, not null
+     * @return the list of Strings, never null
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static List<String> readLines(Reader input) throws IOException {
+        BufferedReader reader = new BufferedReader(input);
+        List<String> list = new ArrayList<String>();
+        String line = reader.readLine();
+        while (line != null) {
+            list.add(line);
+            line = reader.readLine();
+        }
+        return list;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Convert the specified string to an input stream, encoded as bytes
+     * using the default character encoding of the platform.
+     *
+     * @param input the string to convert
+     * @return an input stream
+     * @since Commons IO 1.1
+     */
+    public static InputStream toInputStream(String input) {
+        byte[] bytes = input.getBytes();
+        return new ByteArrayInputStream(bytes);
+    }
+
+    /**
+     * Convert the specified string to an input stream, encoded as bytes
+     * using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     *
+     * @param input the string to convert
+     * @param encoding the encoding to use, null means platform default
+     * @throws IOException if the encoding is invalid
+     * @return an input stream
+     * @since Commons IO 1.1
+     */
+    public static InputStream toInputStream(String input, String encoding) throws IOException {
+        byte[] bytes = encoding != null ? input.getBytes(encoding) : input.getBytes();
+        return new ByteArrayInputStream(bytes);
+    }
+
+    // write byte[]
+    //-----------------------------------------------------------------------
+    /**
+     * Writes bytes from a <code>byte[]</code> to an <code>OutputStream</code>.
+     *
+     * @param data  the byte array to write, do not modify during output,
+     * null ignored
+     * @param output  the <code>OutputStream</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(byte[] data, OutputStream output)
+            throws IOException {
+        if (data != null) {
+            output.write(data);
+        }
+    }
+
+    /**
+     * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code>
+     * using the default character encoding of the platform.
+     * <p>
+     * This method uses {@link String#String(byte[])}.
+     *
+     * @param data  the byte array to write, do not modify during output,
+     * null ignored
+     * @param output  the <code>Writer</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(byte[] data, Writer output) throws IOException {
+        if (data != null) {
+            output.write(new String(data));
+        }
+    }
+
+    /**
+     * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code>
+     * using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method uses {@link String#String(byte[], String)}.
+     *
+     * @param data  the byte array to write, do not modify during output,
+     * null ignored
+     * @param output  the <code>Writer</code> to write to
+     * @param encoding  the encoding to use, null means platform default
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(byte[] data, Writer output, String encoding)
+            throws IOException {
+        if (data != null) {
+            if (encoding == null) {
+                write(data, output);
+            } else {
+                output.write(new String(data, encoding));
+            }
+        }
+    }
+
+    // write char[]
+    //-----------------------------------------------------------------------
+    /**
+     * Writes chars from a <code>char[]</code> to a <code>Writer</code>
+     * using the default character encoding of the platform.
+     *
+     * @param data  the char array to write, do not modify during output,
+     * null ignored
+     * @param output  the <code>Writer</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(char[] data, Writer output) throws IOException {
+        if (data != null) {
+            output.write(data);
+        }
+    }
+
+    /**
+     * Writes chars from a <code>char[]</code> to bytes on an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method uses {@link String#String(char[])} and
+     * {@link String#getBytes()}.
+     *
+     * @param data  the char array to write, do not modify during output,
+     * null ignored
+     * @param output  the <code>OutputStream</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(char[] data, OutputStream output)
+            throws IOException {
+        if (data != null) {
+            output.write(new String(data).getBytes());
+        }
+    }
+
+    /**
+     * Writes chars from a <code>char[]</code> to bytes on an
+     * <code>OutputStream</code> using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method uses {@link String#String(char[])} and
+     * {@link String#getBytes(String)}.
+     *
+     * @param data  the char array to write, do not modify during output,
+     * null ignored
+     * @param output  the <code>OutputStream</code> to write to
+     * @param encoding  the encoding to use, null means platform default
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(char[] data, OutputStream output, String encoding)
+            throws IOException {
+        if (data != null) {
+            if (encoding == null) {
+                write(data, output);
+            } else {
+                output.write(new String(data).getBytes(encoding));
+            }
+        }
+    }
+
+    // write String
+    //-----------------------------------------------------------------------
+    /**
+     * Writes chars from a <code>String</code> to a <code>Writer</code>.
+     *
+     * @param data  the <code>String</code> to write, null ignored
+     * @param output  the <code>Writer</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(String data, Writer output) throws IOException {
+        if (data != null) {
+            output.write(data);
+        }
+    }
+
+    /**
+     * Writes chars from a <code>String</code> to bytes on an
+     * <code>OutputStream</code> using the default character encoding of the
+     * platform.
+     * <p>
+     * This method uses {@link String#getBytes()}.
+     *
+     * @param data  the <code>String</code> to write, null ignored
+     * @param output  the <code>OutputStream</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(String data, OutputStream output)
+            throws IOException {
+        if (data != null) {
+            output.write(data.getBytes());
+        }
+    }
+
+    /**
+     * Writes chars from a <code>String</code> to bytes on an
+     * <code>OutputStream</code> using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method uses {@link String#getBytes(String)}.
+     *
+     * @param data  the <code>String</code> to write, null ignored
+     * @param output  the <code>OutputStream</code> to write to
+     * @param encoding  the encoding to use, null means platform default
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(String data, OutputStream output, String encoding)
+            throws IOException {
+        if (data != null) {
+            if (encoding == null) {
+                write(data, output);
+            } else {
+                output.write(data.getBytes(encoding));
+            }
+        }
+    }
+
+    // write StringBuffer
+    //-----------------------------------------------------------------------
+    /**
+     * Writes chars from a <code>StringBuffer</code> to a <code>Writer</code>.
+     *
+     * @param data  the <code>StringBuffer</code> to write, null ignored
+     * @param output  the <code>Writer</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(StringBuffer data, Writer output)
+            throws IOException {
+        if (data != null) {
+            output.write(data.toString());
+        }
+    }
+
+    /**
+     * Writes chars from a <code>StringBuffer</code> to bytes on an
+     * <code>OutputStream</code> using the default character encoding of the
+     * platform.
+     * <p>
+     * This method uses {@link String#getBytes()}.
+     *
+     * @param data  the <code>StringBuffer</code> to write, null ignored
+     * @param output  the <code>OutputStream</code> to write to
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(StringBuffer data, OutputStream output)
+            throws IOException {
+        if (data != null) {
+            output.write(data.toString().getBytes());
+        }
+    }
+
+    /**
+     * Writes chars from a <code>StringBuffer</code> to bytes on an
+     * <code>OutputStream</code> using the specified character encoding.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method uses {@link String#getBytes(String)}.
+     *
+     * @param data  the <code>StringBuffer</code> to write, null ignored
+     * @param output  the <code>OutputStream</code> to write to
+     * @param encoding  the encoding to use, null means platform default
+     * @throws NullPointerException if output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void write(StringBuffer data, OutputStream output,
+            String encoding) throws IOException {
+        if (data != null) {
+            if (encoding == null) {
+                write(data, output);
+            } else {
+                output.write(data.toString().getBytes(encoding));
+            }
+        }
+    }
+
+    // writeLines
+    //-----------------------------------------------------------------------
+    /**
+     * Writes the <code>toString()</code> value of each item in a collection to
+     * an <code>OutputStream</code> line by line, using the default character
+     * encoding of the platform and the specified line ending.
+     *
+     * @param lines  the lines to write, null entries produce blank lines
+     * @param lineEnding  the line separator to use, null is system default
+     * @param output  the <code>OutputStream</code> to write to, not null, not closed
+     * @throws NullPointerException if the output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void writeLines(Collection<Object> lines, String lineEnding,
+            OutputStream output) throws IOException {
+        if (lines == null) {
+            return;
+        }
+        if (lineEnding == null) {
+            lineEnding = LINE_SEPARATOR;
+        }
+        for (Iterator<Object> it = lines.iterator(); it.hasNext(); ) {
+            Object line = it.next();
+            if (line != null) {
+                output.write(line.toString().getBytes());
+            }
+            output.write(lineEnding.getBytes());
+        }
+    }
+
+    /**
+     * Writes the <code>toString()</code> value of each item in a collection to
+     * an <code>OutputStream</code> line by line, using the specified character
+     * encoding and the specified line ending.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     *
+     * @param lines  the lines to write, null entries produce blank lines
+     * @param lineEnding  the line separator to use, null is system default
+     * @param output  the <code>OutputStream</code> to write to, not null, not closed
+     * @param encoding  the encoding to use, null means platform default
+     * @throws NullPointerException if the output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void writeLines(Collection<Object> lines, String lineEnding,
+            OutputStream output, String encoding) throws IOException {
+        if (encoding == null) {
+            writeLines(lines, lineEnding, output);
+        } else {
+            if (lines == null) {
+                return;
+            }
+            if (lineEnding == null) {
+                lineEnding = LINE_SEPARATOR;
+            }
+            for (Iterator<Object> it = lines.iterator(); it.hasNext(); ) {
+                Object line = it.next();
+                if (line != null) {
+                    output.write(line.toString().getBytes(encoding));
+                }
+                output.write(lineEnding.getBytes(encoding));
+            }
+        }
+    }
+
+    /**
+     * Writes the <code>toString()</code> value of each item in a collection to
+     * a <code>Writer</code> line by line, using the specified line ending.
+     *
+     * @param lines  the lines to write, null entries produce blank lines
+     * @param lineEnding  the line separator to use, null is system default
+     * @param writer  the <code>Writer</code> to write to, not null, not closed
+     * @throws NullPointerException if the input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void writeLines(Collection<Object> lines, String lineEnding,
+            Writer writer) throws IOException {
+        if (lines == null) {
+            return;
+        }
+        if (lineEnding == null) {
+            lineEnding = LINE_SEPARATOR;
+        }
+        for (Iterator<Object> it = lines.iterator(); it.hasNext(); ) {
+            Object line = it.next();
+            if (line != null) {
+                writer.write(line.toString());
+            }
+            writer.write(lineEnding);
+        }
+    }
+
+    // copy from InputStream
+    //-----------------------------------------------------------------------
+    /**
+     * Copy bytes from an <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     * Large streams (over 2GB) will return a bytes copied value of
+     * <code>-1</code> after the copy has completed since the correct
+     * number of bytes cannot be returned as an int. For large streams
+     * use the <code>copyLarge(InputStream, OutputStream)</code> method.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @param output  the <code>OutputStream</code> to write to
+     * @return the number of bytes copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @throws ArithmeticException if the byte count is too large
+     * @since Commons IO 1.1
+     */
+    public static int copy(InputStream input, OutputStream output) throws IOException {
+        long count = copyLarge(input, output);
+        if (count > Integer.MAX_VALUE) {
+            return -1;
+        }
+        return (int) count;
+    }
+
+    /**
+     * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @param output  the <code>OutputStream</code> to write to
+     * @return the number of bytes copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.3
+     */
+    public static long copyLarge(InputStream input, OutputStream output)
+            throws IOException {
+        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+        long count = 0;
+        int n = 0;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    /**
+     * Copy bytes from an <code>InputStream</code> to chars on a
+     * <code>Writer</code> using the default character encoding of the platform.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     * This method uses {@link InputStreamReader}.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @param output  the <code>Writer</code> to write to
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void copy(InputStream input, Writer output)
+            throws IOException {
+        InputStreamReader in = new InputStreamReader(input);
+        copy(in, output);
+    }
+
+    /**
+     * Copy bytes from an <code>InputStream</code> to chars on a
+     * <code>Writer</code> using the specified character encoding.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * This method uses {@link InputStreamReader}.
+     *
+     * @param input  the <code>InputStream</code> to read from
+     * @param output  the <code>Writer</code> to write to
+     * @param encoding  the encoding to use, null means platform default
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void copy(InputStream input, Writer output, String encoding)
+            throws IOException {
+        if (encoding == null) {
+            copy(input, output);
+        } else {
+            InputStreamReader in = new InputStreamReader(input, encoding);
+            copy(in, output);
+        }
+    }
+
+    // copy from Reader
+    //-----------------------------------------------------------------------
+    /**
+     * Copy chars from a <code>Reader</code> to a <code>Writer</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p>
+     * Large streams (over 2GB) will return a chars copied value of
+     * <code>-1</code> after the copy has completed since the correct
+     * number of chars cannot be returned as an int. For large streams
+     * use the <code>copyLarge(Reader, Writer)</code> method.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @param output  the <code>Writer</code> to write to
+     * @return the number of characters copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @throws ArithmeticException if the character count is too large
+     * @since Commons IO 1.1
+     */
+    public static int copy(Reader input, Writer output) throws IOException {
+        long count = copyLarge(input, output);
+        if (count > Integer.MAX_VALUE) {
+            return -1;
+        }
+        return (int) count;
+    }
+
+    /**
+     * Copy chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @param output  the <code>Writer</code> to write to
+     * @return the number of characters copied
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.3
+     */
+    public static long copyLarge(Reader input, Writer output) throws IOException {
+        char[] buffer = new char[DEFAULT_BUFFER_SIZE];
+        long count = 0;
+        int n = 0;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    /**
+     * Copy chars from a <code>Reader</code> to bytes on an
+     * <code>OutputStream</code> using the default character encoding of the
+     * platform, and calling flush.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p>
+     * Due to the implementation of OutputStreamWriter, this method performs a
+     * flush.
+     * <p>
+     * This method uses {@link OutputStreamWriter}.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @param output  the <code>OutputStream</code> to write to
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void copy(Reader input, OutputStream output)
+            throws IOException {
+        OutputStreamWriter out = new OutputStreamWriter(output);
+        copy(input, out);
+        // XXX Unless anyone is planning on rewriting OutputStreamWriter, we
+        // have to flush here.
+        out.flush();
+    }
+
+    /**
+     * Copy chars from a <code>Reader</code> to bytes on an
+     * <code>OutputStream</code> using the specified character encoding, and
+     * calling flush.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedReader</code>.
+     * <p>
+     * Character encoding names can be found at
+     * <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
+     * <p>
+     * Due to the implementation of OutputStreamWriter, this method performs a
+     * flush.
+     * <p>
+     * This method uses {@link OutputStreamWriter}.
+     *
+     * @param input  the <code>Reader</code> to read from
+     * @param output  the <code>OutputStream</code> to write to
+     * @param encoding  the encoding to use, null means platform default
+     * @throws NullPointerException if the input or output is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static void copy(Reader input, OutputStream output, String encoding)
+            throws IOException {
+        if (encoding == null) {
+            copy(input, output);
+        } else {
+            OutputStreamWriter out = new OutputStreamWriter(output, encoding);
+            copy(input, out);
+            // XXX Unless anyone is planning on rewriting OutputStreamWriter,
+            // we have to flush here.
+            out.flush();
+        }
+    }
+
+    // content equals
+    //-----------------------------------------------------------------------
+    /**
+     * Compare the contents of two Streams to determine if they are equal or
+     * not.
+     * <p>
+     * This method buffers the input internally using
+     * <code>BufferedInputStream</code> if they are not already buffered.
+     *
+     * @param input1  the first stream
+     * @param input2  the second stream
+     * @return true if the content of the streams are equal or they both don't
+     * exist, false otherwise
+     * @throws NullPointerException if either input is null
+     * @throws IOException if an I/O error occurs
+     */
+    public static boolean contentEquals(InputStream input1, InputStream input2)
+            throws IOException {
+        if (!(input1 instanceof BufferedInputStream)) {
+            input1 = new BufferedInputStream(input1);
+        }
+        if (!(input2 instanceof BufferedInputStream)) {
+            input2 = new BufferedInputStream(input2);
+        }
+
+        int ch = input1.read();
+        while (-1 != ch) {
+            int ch2 = input2.read();
+            if (ch != ch2) {
+                return false;
+            }
+            ch = input1.read();
+        }
+
+        int ch2 = input2.read();
+        return (ch2 == -1);
+    }
+
+    /**
+     * Compare the contents of two Readers to determine if they are equal or
+     * not.
+     * <p>
+     * This method buffers the input internally using
+     * <code>BufferedReader</code> if they are not already buffered.
+     *
+     * @param input1  the first reader
+     * @param input2  the second reader
+     * @return true if the content of the readers are equal or they both don't
+     * exist, false otherwise
+     * @throws NullPointerException if either input is null
+     * @throws IOException if an I/O error occurs
+     * @since Commons IO 1.1
+     */
+    public static boolean contentEquals(Reader input1, Reader input2)
+            throws IOException {
+        if (!(input1 instanceof BufferedReader)) {
+            input1 = new BufferedReader(input1);
+        }
+        if (!(input2 instanceof BufferedReader)) {
+            input2 = new BufferedReader(input2);
+        }
+
+        int ch = input1.read();
+        while (-1 != ch) {
+            int ch2 = input2.read();
+            if (ch != ch2) {
+                return false;
+            }
+            ch = input1.read();
+        }
+
+        int ch2 = input2.read();
+        return (ch2 == -1);
+    }
+
+}
diff --git a/src/org/apache/james/mime4j/BodyDescriptor.java b/src/org/apache/james/mime4j/BodyDescriptor.java
new file mode 100644
index 0000000..867c43d
--- /dev/null
+++ b/src/org/apache/james/mime4j/BodyDescriptor.java
@@ -0,0 +1,392 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Encapsulates the values of the MIME-specific header fields
+ * (which starts with <code>Content-</code>).
+ *
+ *
+ * @version $Id: BodyDescriptor.java,v 1.4 2005/02/11 10:08:37 ntherning Exp $
+ */
+public class BodyDescriptor {
+    private static Log log = LogFactory.getLog(BodyDescriptor.class);
+
+    private String mimeType = "text/plain";
+    private String boundary = null;
+    private String charset = "us-ascii";
+    private String transferEncoding = "7bit";
+    private Map<String, String> parameters = new HashMap<String, String>();
+    private boolean contentTypeSet = false;
+    private boolean contentTransferEncSet = false;
+
+    /**
+     * Creates a new root <code>BodyDescriptor</code> instance.
+     */
+    public BodyDescriptor() {
+        this(null);
+    }
+
+    /**
+     * Creates a new <code>BodyDescriptor</code> instance.
+     *
+     * @param parent the descriptor of the parent or <code>null</code> if this
+     *        is the root descriptor.
+     */
+    public BodyDescriptor(BodyDescriptor parent) {
+        if (parent != null && parent.isMimeType("multipart/digest")) {
+            mimeType = "message/rfc822";
+        } else {
+            mimeType = "text/plain";
+        }
+    }
+
+    /**
+     * Should be called for each <code>Content-</code> header field of
+     * a MIME message or part.
+     *
+     * @param name the field name.
+     * @param value the field value.
+     */
+    public void addField(String name, String value) {
+
+        name = name.trim().toLowerCase();
+
+        if (name.equals("content-transfer-encoding") && !contentTransferEncSet) {
+            contentTransferEncSet = true;
+
+            value = value.trim().toLowerCase();
+            if (value.length() > 0) {
+                transferEncoding = value;
+            }
+
+        } else if (name.equals("content-type") && !contentTypeSet) {
+            contentTypeSet = true;
+
+            value = value.trim();
+
+            /*
+             * Unfold Content-Type value
+             */
+            StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < value.length(); i++) {
+                char c = value.charAt(i);
+                if (c == '\r' || c == '\n') {
+                    continue;
+                }
+                sb.append(c);
+            }
+
+            Map<String, String> params = getHeaderParams(sb.toString());
+
+            String main = params.get("");
+            if (main != null) {
+                main = main.toLowerCase().trim();
+                int index = main.indexOf('/');
+                boolean valid = false;
+                if (index != -1) {
+                    String type = main.substring(0, index).trim();
+                    String subtype = main.substring(index + 1).trim();
+                    if (type.length() > 0 && subtype.length() > 0) {
+                        main = type + "/" + subtype;
+                        valid = true;
+                    }
+                }
+
+                if (!valid) {
+                    main = null;
+                }
+            }
+            String b = params.get("boundary");
+
+            if (main != null
+                    && ((main.startsWith("multipart/") && b != null)
+                            || !main.startsWith("multipart/"))) {
+
+                mimeType = main;
+            }
+
+            if (isMultipart()) {
+                boundary = b;
+            }
+
+            String c = params.get("charset");
+            if (c != null) {
+                c = c.trim();
+                if (c.length() > 0) {
+                    charset = c.toLowerCase();
+                }
+            }
+
+            /*
+             * Add all other parameters to parameters.
+             */
+            parameters.putAll(params);
+            parameters.remove("");
+            parameters.remove("boundary");
+            parameters.remove("charset");
+        }
+    }
+
+    private Map<String, String> getHeaderParams(String headerValue) {
+        Map<String, String> result = new HashMap<String, String>();
+
+        // split main value and parameters
+        String main;
+        String rest;
+        if (headerValue.indexOf(";") == -1) {
+            main = headerValue;
+            rest = null;
+        } else {
+            main = headerValue.substring(0, headerValue.indexOf(";"));
+            rest = headerValue.substring(main.length() + 1);
+        }
+
+        result.put("", main);
+        if (rest != null) {
+            char[] chars = rest.toCharArray();
+            StringBuffer paramName = new StringBuffer();
+            StringBuffer paramValue = new StringBuffer();
+
+            final byte READY_FOR_NAME = 0;
+            final byte IN_NAME = 1;
+            final byte READY_FOR_VALUE = 2;
+            final byte IN_VALUE = 3;
+            final byte IN_QUOTED_VALUE = 4;
+            final byte VALUE_DONE = 5;
+            final byte ERROR = 99;
+
+            byte state = READY_FOR_NAME;
+            boolean escaped = false;
+            for (int i = 0; i < chars.length; i++) {
+                char c = chars[i];
+
+                switch (state) {
+                    case ERROR:
+                        if (c == ';')
+                            state = READY_FOR_NAME;
+                        break;
+
+                    case READY_FOR_NAME:
+                        if (c == '=') {
+                            log.error("Expected header param name, got '='");
+                            state = ERROR;
+                            break;
+                        }
+
+                        paramName = new StringBuffer();
+                        paramValue = new StringBuffer();
+
+                        state = IN_NAME;
+                        // $FALL-THROUGH$
+
+                    case IN_NAME:
+                        if (c == '=') {
+                            if (paramName.length() == 0)
+                                state = ERROR;
+                            else
+                                state = READY_FOR_VALUE;
+                            break;
+                        }
+
+                        // not '='... just add to name
+                        paramName.append(c);
+                        break;
+
+                    case READY_FOR_VALUE:
+                        boolean fallThrough = false;
+                        switch (c) {
+                            case ' ':
+                            case '\t':
+                                break;  // ignore spaces, especially before '"'
+
+                            case '"':
+                                state = IN_QUOTED_VALUE;
+                                break;
+
+                            default:
+                                state = IN_VALUE;
+                                fallThrough = true;
+                                break;
+                        }
+                        if (!fallThrough)
+                            break;
+
+                        // $FALL-THROUGH$
+
+                    case IN_VALUE:
+                        fallThrough = false;
+                        switch (c) {
+                            case ';':
+                            case ' ':
+                            case '\t':
+                                result.put(
+                                   paramName.toString().trim().toLowerCase(),
+                                   paramValue.toString().trim());
+                                state = VALUE_DONE;
+                                fallThrough = true;
+                                break;
+                            default:
+                                paramValue.append(c);
+                                break;
+                        }
+                        if (!fallThrough)
+                            break;
+
+                        // $FALL-THROUGH$
+
+                    case VALUE_DONE:
+                        switch (c) {
+                            case ';':
+                                state = READY_FOR_NAME;
+                                break;
+
+                            case ' ':
+                            case '\t':
+                                break;
+
+                            default:
+                                state = ERROR;
+                                break;
+                        }
+                        break;
+
+                    case IN_QUOTED_VALUE:
+                        switch (c) {
+                            case '"':
+                                if (!escaped) {
+                                    // don't trim quoted strings; the spaces could be intentional.
+                                    result.put(
+                                            paramName.toString().trim().toLowerCase(),
+                                            paramValue.toString());
+                                    state = VALUE_DONE;
+                                } else {
+                                    escaped = false;
+                                    paramValue.append(c);
+                                }
+                                break;
+
+                            case '\\':
+                                if (escaped) {
+                                    paramValue.append('\\');
+                                }
+                                escaped = !escaped;
+                                break;
+
+                            default:
+                                if (escaped) {
+                                    paramValue.append('\\');
+                                }
+                                escaped = false;
+                                paramValue.append(c);
+                                break;
+                        }
+                        break;
+
+                }
+            }
+
+            // done looping.  check if anything is left over.
+            if (state == IN_VALUE) {
+                result.put(
+                        paramName.toString().trim().toLowerCase(),
+                        paramValue.toString().trim());
+            }
+        }
+
+        return result;
+    }
+
+
+    public boolean isMimeType(String mimeType) {
+        return this.mimeType.equals(mimeType.toLowerCase());
+    }
+
+    /**
+     * Return true if the BodyDescriptor belongs to a message
+     */
+    public boolean isMessage() {
+        return mimeType.equals("message/rfc822");
+    }
+
+    /**
+     * Return true if the BodyDescripotro belongs to a multipart
+     */
+    public boolean isMultipart() {
+        return mimeType.startsWith("multipart/");
+    }
+
+    /**
+     * Return the MimeType
+     */
+    public String getMimeType() {
+        return mimeType;
+    }
+
+    /**
+     * Return the boundary
+     */
+    public String getBoundary() {
+        return boundary;
+    }
+
+    /**
+     * Return the charset
+     */
+    public String getCharset() {
+        return charset;
+    }
+
+    /**
+     * Return all parameters for the BodyDescriptor
+     */
+    public Map<String, String> getParameters() {
+        return parameters;
+    }
+
+    /**
+     * Return the TransferEncoding
+     */
+    public String getTransferEncoding() {
+        return transferEncoding;
+    }
+
+    /**
+     * Return true if it's base64 encoded
+     */
+    public boolean isBase64Encoded() {
+        return "base64".equals(transferEncoding);
+    }
+
+    /**
+     * Return true if it's quoted-printable
+     */
+    public boolean isQuotedPrintableEncoded() {
+        return "quoted-printable".equals(transferEncoding);
+    }
+
+    @Override
+    public String toString() {
+        return mimeType;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/CloseShieldInputStream.java b/src/org/apache/james/mime4j/CloseShieldInputStream.java
new file mode 100644
index 0000000..d9f3b07
--- /dev/null
+++ b/src/org/apache/james/mime4j/CloseShieldInputStream.java
@@ -0,0 +1,129 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * InputStream that shields its underlying input stream from
+ * being closed.
+ *
+ *
+ * @version $Id: CloseShieldInputStream.java,v 1.2 2004/10/02 12:41:10 ntherning Exp $
+ */
+public class CloseShieldInputStream extends InputStream {
+
+    /**
+     * Underlying InputStream
+     */
+    private InputStream is;
+
+    public CloseShieldInputStream(InputStream is) {
+        this.is = is;
+    }
+
+    public InputStream getUnderlyingStream() {
+        return is;
+    }
+
+    /**
+     * @see java.io.InputStream#read()
+     */
+    public int read() throws IOException {
+        checkIfClosed();
+        return is.read();
+    }
+
+    /**
+     * @see java.io.InputStream#available()
+     */
+    public int available() throws IOException {
+        checkIfClosed();
+        return is.available();
+    }
+
+
+    /**
+     * Set the underlying InputStream to null
+     */
+    public void close() throws IOException {
+        is = null;
+    }
+
+    /**
+     * @see java.io.FilterInputStream#reset()
+     */
+    public synchronized void reset() throws IOException {
+        checkIfClosed();
+        is.reset();
+    }
+
+    /**
+     * @see java.io.FilterInputStream#markSupported()
+     */
+    public boolean markSupported() {
+        if (is == null)
+            return false;
+        return is.markSupported();
+    }
+
+    /**
+     * @see java.io.FilterInputStream#mark(int)
+     */
+    public synchronized void mark(int readlimit) {
+        if (is != null)
+            is.mark(readlimit);
+    }
+
+    /**
+     * @see java.io.FilterInputStream#skip(long)
+     */
+    public long skip(long n) throws IOException {
+        checkIfClosed();
+        return is.skip(n);
+    }
+
+    /**
+     * @see java.io.FilterInputStream#read(byte[])
+     */
+    public int read(byte b[]) throws IOException {
+        checkIfClosed();
+        return is.read(b);
+    }
+
+    /**
+     * @see java.io.FilterInputStream#read(byte[], int, int)
+     */
+    public int read(byte b[], int off, int len) throws IOException {
+        checkIfClosed();
+        return is.read(b, off, len);
+    }
+
+    /**
+     * Check if the underlying InputStream is null. If so throw an Exception
+     *
+     * @throws IOException if the underlying InputStream is null
+     */
+    private void checkIfClosed() throws IOException {
+        if (is == null)
+            throw new IOException("Stream is closed");
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/ContentHandler.java b/src/org/apache/james/mime4j/ContentHandler.java
new file mode 100644
index 0000000..b437e73
--- /dev/null
+++ b/src/org/apache/james/mime4j/ContentHandler.java
@@ -0,0 +1,177 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * <p>
+ * Receives notifications of the content of a plain RFC822 or MIME message.
+ * Implement this interface and register an instance of that implementation
+ * with a <code>MimeStreamParser</code> instance using its
+ * {@link org.apache.james.mime4j.MimeStreamParser#setContentHandler(ContentHandler)}
+ * method. The parser uses the <code>ContentHandler</code> instance to report
+ * basic message-related events like the start and end of the body of a
+ * part in a multipart MIME entity.
+ * </p>
+ * <p>
+ * Events will be generated in the order the corresponding elements occur in
+ * the message stream parsed by the parser. E.g.:
+ * <pre>
+ *      startMessage()
+ *          startHeader()
+ *              field(...)
+ *              field(...)
+ *              ...
+ *          endHeader()
+ *          startMultipart()
+ *              preamble(...)
+ *              startBodyPart()
+ *                  startHeader()
+ *                      field(...)
+ *                      field(...)
+ *                      ...
+ *                  endHeader()
+ *                  body()
+ *              endBodyPart()
+ *              startBodyPart()
+ *                  startHeader()
+ *                      field(...)
+ *                      field(...)
+ *                      ...
+ *                  endHeader()
+ *                  body()
+ *              endBodyPart()
+ *              epilogue(...)
+ *          endMultipart()
+ *      endMessage()
+ * </pre>
+ * The above shows an example of a MIME message consisting of a multipart
+ * body containing two body parts.
+ * </p>
+ * <p>
+ * See MIME RFCs 2045-2049 for more information on the structure of MIME
+ * messages and RFC 822 and 2822 for the general structure of Internet mail
+ * messages.
+ * </p>
+ *
+ *
+ * @version $Id: ContentHandler.java,v 1.3 2004/10/02 12:41:10 ntherning Exp $
+ */
+public interface ContentHandler {
+    /**
+     * Called when a new message starts (a top level message or an embedded
+     * rfc822 message).
+     */
+    void startMessage();
+
+    /**
+     * Called when a message ends.
+     */
+    void endMessage();
+
+    /**
+     * Called when a new body part starts inside a
+     * <code>multipart/*</code> entity.
+     */
+    void startBodyPart();
+
+    /**
+     * Called when a body part ends.
+     */
+    void endBodyPart();
+
+    /**
+     * Called when a header (of a message or body part) is about to be parsed.
+     */
+    void startHeader();
+
+    /**
+     * Called for each field of a header.
+     *
+     * @param fieldData the raw contents of the field
+     *        (<code>Field-Name: field value</code>). The value will not be
+     *        unfolded.
+     */
+    void field(String fieldData);
+
+    /**
+     * Called when there are no more header fields in a message or body part.
+     */
+    void endHeader();
+
+    /**
+     * Called for the preamble (whatever comes before the first body part)
+     * of a <code>multipart/*</code> entity.
+     *
+     * @param is used to get the contents of the preamble.
+     * @throws IOException should be thrown on I/O errors.
+     */
+    void preamble(InputStream is) throws IOException;
+
+    /**
+     * Called for the epilogue (whatever comes after the final body part)
+     * of a <code>multipart/*</code> entity.
+     *
+     * @param is used to get the contents of the epilogue.
+     * @throws IOException should be thrown on I/O errors.
+     */
+    void epilogue(InputStream is) throws IOException;
+
+    /**
+     * Called when the body of a multipart entity is about to be parsed.
+     *
+     * @param bd encapsulates the values (either read from the
+     *        message stream or, if not present, determined implictly
+     *        as described in the
+     *        MIME rfc:s) of the <code>Content-Type</code> and
+     *        <code>Content-Transfer-Encoding</code> header fields.
+     */
+    void startMultipart(BodyDescriptor bd);
+
+    /**
+     * Called when the body of an entity has been parsed.
+     */
+    void endMultipart();
+
+    /**
+     * Called when the body of a discrete (non-multipart) entity is about to
+     * be parsed.
+     *
+     * @param bd see {@link #startMultipart(BodyDescriptor)}
+     * @param is the contents of the body. NOTE: this is the raw body contents
+     *           - it will not be decoded if encoded. The <code>bd</code>
+     *           parameter should be used to determine how the stream data
+     *           should be decoded.
+     * @throws IOException should be thrown on I/O errors.
+     */
+    void body(BodyDescriptor bd, InputStream is) throws IOException;
+
+    /**
+     * Called when a new entity (message or body part) starts and the
+     * parser is in <code>raw</code> mode.
+     *
+     * @param is the raw contents of the entity.
+     * @throws IOException should be thrown on I/O errors.
+     * @see MimeStreamParser#setRaw(boolean)
+     */
+    void raw(InputStream is) throws IOException;
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/EOLConvertingInputStream.java b/src/org/apache/james/mime4j/EOLConvertingInputStream.java
new file mode 100644
index 0000000..d6ef706
--- /dev/null
+++ b/src/org/apache/james/mime4j/EOLConvertingInputStream.java
@@ -0,0 +1,139 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+
+/**
+ * InputStream which converts <code>\r</code>
+ * bytes not followed by <code>\n</code> and <code>\n</code> not
+ * preceded by <code>\r</code> to <code>\r\n</code>.
+ *
+ *
+ * @version $Id: EOLConvertingInputStream.java,v 1.4 2004/11/29 13:15:42 ntherning Exp $
+ */
+public class EOLConvertingInputStream extends InputStream {
+    /** Converts single '\r' to '\r\n' */
+    public static final int CONVERT_CR   = 1;
+    /** Converts single '\n' to '\r\n' */
+    public static final int CONVERT_LF   = 2;
+    /** Converts single '\r' and '\n' to '\r\n' */
+    public static final int CONVERT_BOTH = 3;
+
+    private PushbackInputStream in = null;
+    private int previous = 0;
+    private int flags = CONVERT_BOTH;
+    private int size = 0;
+    private int pos = 0;
+    private int nextTenPctPos;
+    private int tenPctSize;
+    private Callback callback;
+
+    public interface Callback {
+        public void report(int bytesRead);
+    }
+
+    /**
+     * Creates a new <code>EOLConvertingInputStream</code>
+     * instance converting bytes in the given <code>InputStream</code>.
+     * The flag <code>CONVERT_BOTH</code> is the default.
+     *
+     * @param in the <code>InputStream</code> to read from.
+     */
+    public EOLConvertingInputStream(InputStream _in) {
+        super();
+        in = new PushbackInputStream(_in, 2);
+    }
+
+    /**
+     * Creates a new <code>EOLConvertingInputStream</code>
+     * instance converting bytes in the given <code>InputStream</code>.
+     *
+     * @param _in the <code>InputStream</code> to read from.
+     * @param _size the size of the input stream (need not be exact)
+     * @param _callback a callback reporting when each 10% of stream's size is reached
+     */
+    public EOLConvertingInputStream(InputStream _in, int _size, Callback _callback) {
+        this(_in);
+        size = _size;
+        tenPctSize = size / 10;
+        nextTenPctPos = tenPctSize;
+        callback = _callback;
+    }
+
+    /**
+     * Closes the underlying stream.
+     *
+     * @throws IOException on I/O errors.
+     */
+    public void close() throws IOException {
+        in.close();
+    }
+
+    private int readByte() throws IOException {
+        int b = in.read();
+        if (b != -1) {
+            if (callback != null && pos++ == nextTenPctPos) {
+                nextTenPctPos += tenPctSize;
+                if (callback != null) {
+                    callback.report(pos);
+                }
+            }
+        }
+        return b;
+    }
+
+    private void unreadByte(int c) throws IOException {
+        in.unread(c);
+        pos--;
+    }
+
+    /**
+     * @see java.io.InputStream#read()
+     */
+    public int read() throws IOException {
+        int b = readByte();
+
+        if (b == -1) {
+            pos = size;
+            return -1;
+        }
+
+        if ((flags & CONVERT_CR) != 0 && b == '\r') {
+            int c = readByte();
+            if (c != -1) {
+                unreadByte(c);
+            }
+            if (c != '\n') {
+                unreadByte('\n');
+            }
+        } else if ((flags & CONVERT_LF) != 0 && b == '\n' && previous != '\r') {
+            b = '\r';
+            unreadByte('\n');
+        }
+
+        previous = b;
+
+        return b;
+    }
+
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/Log.java b/src/org/apache/james/mime4j/Log.java
new file mode 100644
index 0000000..5eeead5
--- /dev/null
+++ b/src/org/apache/james/mime4j/Log.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.james.mime4j;
+
+/**
+ * Empty stub for the apache logging library.
+ */
+public class Log {
+    private static final String LOG_TAG = "Email Log";
+
+    public Log(Class mClazz) {
+    }
+
+    public boolean isDebugEnabled() {
+        return false;
+    }
+
+    public boolean isErrorEnabled() {
+        return true;
+    }
+
+    public boolean isFatalEnabled() {
+        return true;
+    }
+
+    public boolean isInfoEnabled() {
+        return false;
+    }
+
+    public boolean isTraceEnabled() {
+        return false;
+    }
+
+    public boolean isWarnEnabled() {
+        return true;
+    }
+
+    public void trace(Object message) {
+        if (!isTraceEnabled()) return;
+        android.util.Log.v(LOG_TAG, toString(message, null));
+    }
+
+    public void trace(Object message, Throwable t) {
+        if (!isTraceEnabled()) return;
+        android.util.Log.v(LOG_TAG, toString(message, t));
+    }
+
+    public void debug(Object message) {
+        if (!isDebugEnabled()) return;
+        android.util.Log.d(LOG_TAG, toString(message, null));
+    }
+
+    public void debug(Object message, Throwable t) {
+        if (!isDebugEnabled()) return;
+        android.util.Log.d(LOG_TAG, toString(message, t));
+    }
+
+    public void info(Object message) {
+        if (!isInfoEnabled()) return;
+        android.util.Log.i(LOG_TAG, toString(message, null));
+    }
+
+    public void info(Object message, Throwable t) {
+        if (!isInfoEnabled()) return;
+        android.util.Log.i(LOG_TAG, toString(message, t));
+    }
+
+    public void warn(Object message) {
+        android.util.Log.w(LOG_TAG, toString(message, null));
+    }
+
+    public void warn(Object message, Throwable t) {
+        android.util.Log.w(LOG_TAG, toString(message, t));
+    }
+
+    public void error(Object message) {
+        android.util.Log.e(LOG_TAG, toString(message, null));
+    }
+
+    public void error(Object message, Throwable t) {
+        android.util.Log.e(LOG_TAG, toString(message, t));
+    }
+
+    public void fatal(Object message) {
+        android.util.Log.e(LOG_TAG, toString(message, null));
+    }
+
+    public void fatal(Object message, Throwable t) {
+        android.util.Log.e(LOG_TAG, toString(message, t));
+    }
+
+    private static String toString(Object o, Throwable t) {
+        String m = (o == null) ? "(null)" : o.toString();
+        if (t == null) {
+            return m;
+        } else {
+            return m + " " + t.getMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/LogFactory.java b/src/org/apache/james/mime4j/LogFactory.java
new file mode 100644
index 0000000..ed6e3de
--- /dev/null
+++ b/src/org/apache/james/mime4j/LogFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.james.mime4j;
+
+/**
+ * Empty stub for the apache logging library.
+ */
+public final class LogFactory {
+    private LogFactory() {
+    }
+
+    public static Log getLog(Class clazz) {
+        return new Log(clazz);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/MimeBoundaryInputStream.java b/src/org/apache/james/mime4j/MimeBoundaryInputStream.java
new file mode 100644
index 0000000..c6d6f24
--- /dev/null
+++ b/src/org/apache/james/mime4j/MimeBoundaryInputStream.java
@@ -0,0 +1,184 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+
+/**
+ * Stream that constrains itself to a single MIME body part.
+ * After the stream ends (i.e. read() returns -1) {@link #hasMoreParts()}
+ * can be used to determine if a final boundary has been seen or not.
+ * If {@link #parentEOF()} is <code>true</code> an unexpected end of stream
+ * has been detected in the parent stream.
+ *
+ *
+ *
+ * @version $Id: MimeBoundaryInputStream.java,v 1.2 2004/11/29 13:15:42 ntherning Exp $
+ */
+public class MimeBoundaryInputStream extends InputStream {
+
+    private PushbackInputStream s = null;
+    private byte[] boundary = null;
+    private boolean first = true;
+    private boolean eof = false;
+    private boolean parenteof = false;
+    private boolean moreParts = true;
+
+    /**
+     * Creates a new MimeBoundaryInputStream.
+     * @param s The underlying stream.
+     * @param boundary Boundary string (not including leading hyphens).
+     */
+    public MimeBoundaryInputStream(InputStream s, String boundary)
+            throws IOException {
+
+        this.s = new PushbackInputStream(s, boundary.length() + 4);
+
+        boundary = "--" + boundary;
+        this.boundary = new byte[boundary.length()];
+        for (int i = 0; i < this.boundary.length; i++) {
+            this.boundary[i] = (byte) boundary.charAt(i);
+        }
+
+        /*
+         * By reading one byte we will update moreParts to be as expected
+         * before any bytes have been read.
+         */
+        int b = read();
+        if (b != -1) {
+            this.s.unread(b);
+        }
+    }
+
+    /**
+     * Closes the underlying stream.
+     *
+     * @throws IOException on I/O errors.
+     */
+    public void close() throws IOException {
+        s.close();
+    }
+
+    /**
+     * Determines if the underlying stream has more parts (this stream has
+     * not seen an end boundary).
+     *
+     * @return <code>true</code> if there are more parts in the underlying
+     *         stream, <code>false</code> otherwise.
+     */
+    public boolean hasMoreParts() {
+        return moreParts;
+    }
+
+    /**
+     * Determines if the parent stream has reached EOF
+     *
+     * @return <code>true</code>  if EOF has been reached for the parent stream,
+     *         <code>false</code> otherwise.
+     */
+    public boolean parentEOF() {
+        return parenteof;
+    }
+
+    /**
+     * Consumes all unread bytes of this stream. After a call to this method
+     * this stream will have reached EOF.
+     *
+     * @throws IOException on I/O errors.
+     */
+    public void consume() throws IOException {
+        while (read() != -1) {
+        }
+    }
+
+    /**
+     * @see java.io.InputStream#read()
+     */
+    public int read() throws IOException {
+        if (eof) {
+            return -1;
+        }
+
+        if (first) {
+            first = false;
+            if (matchBoundary()) {
+                return -1;
+            }
+        }
+
+        int b1 = s.read();
+        int b2 = s.read();
+
+        if (b1 == '\r' && b2 == '\n') {
+            if (matchBoundary()) {
+                return -1;
+            }
+        }
+
+        if (b2 != -1) {
+            s.unread(b2);
+        }
+
+        parenteof = b1 == -1;
+        eof = parenteof;
+
+        return b1;
+    }
+
+    private boolean matchBoundary() throws IOException {
+
+        for (int i = 0; i < boundary.length; i++) {
+            int b = s.read();
+            if (b != boundary[i]) {
+                if (b != -1) {
+                    s.unread(b);
+                }
+                for (int j = i - 1; j >= 0; j--) {
+                    s.unread(boundary[j]);
+                }
+                return false;
+            }
+        }
+
+        /*
+         * We have a match. Is it an end boundary?
+         */
+        int prev = s.read();
+        int curr = s.read();
+        moreParts = !(prev == '-' && curr == '-');
+        do {
+            if (curr == '\n' && prev == '\r') {
+                break;
+            }
+            prev = curr;
+        } while ((curr = s.read()) != -1);
+
+        if (curr == -1) {
+            moreParts = false;
+            parenteof = true;
+        }
+
+        eof = true;
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/MimeStreamParser.java b/src/org/apache/james/mime4j/MimeStreamParser.java
new file mode 100644
index 0000000..a8aad5a
--- /dev/null
+++ b/src/org/apache/james/mime4j/MimeStreamParser.java
@@ -0,0 +1,324 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j;
+
+import org.apache.james.mime4j.decoder.Base64InputStream;
+import org.apache.james.mime4j.decoder.QuotedPrintableInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.BitSet;
+import java.util.LinkedList;
+
+/**
+ * <p>
+ * Parses MIME (or RFC822) message streams of bytes or characters and reports
+ * parsing events to a <code>ContentHandler</code> instance.
+ * </p>
+ * <p>
+ * Typical usage:<br/>
+ * <pre>
+ *      ContentHandler handler = new MyHandler();
+ *      MimeStreamParser parser = new MimeStreamParser();
+ *      parser.setContentHandler(handler);
+ *      parser.parse(new BufferedInputStream(new FileInputStream("mime.msg")));
+ * </pre>
+ * <strong>NOTE:</strong> All lines must end with CRLF
+ * (<code>\r\n</code>). If you are unsure of the line endings in your stream
+ * you should wrap it in a {@link org.apache.james.mime4j.EOLConvertingInputStream} instance.
+ *
+ *
+ * @version $Id: MimeStreamParser.java,v 1.8 2005/02/11 10:12:02 ntherning Exp $
+ */
+public class MimeStreamParser {
+    private static final Log log = LogFactory.getLog(MimeStreamParser.class);
+
+    private static BitSet fieldChars = null;
+
+    private RootInputStream rootStream = null;
+    private LinkedList<BodyDescriptor> bodyDescriptors = new LinkedList<BodyDescriptor>();
+    private ContentHandler handler = null;
+    private boolean raw = false;
+    private boolean prematureEof = false;
+
+    static {
+        fieldChars = new BitSet();
+        for (int i = 0x21; i <= 0x39; i++) {
+            fieldChars.set(i);
+        }
+        for (int i = 0x3b; i <= 0x7e; i++) {
+            fieldChars.set(i);
+        }
+    }
+
+    /**
+     * Creates a new <code>MimeStreamParser</code> instance.
+     */
+    public MimeStreamParser() {
+    }
+
+    /**
+     * Parses a stream of bytes containing a MIME message.
+     *
+     * @param is the stream to parse.
+     * @throws IOException on I/O errors.
+     */
+    public void parse(InputStream is) throws IOException {
+        rootStream = new RootInputStream(is);
+        parseMessage(rootStream);
+    }
+
+    /**
+     * Determines if this parser is currently in raw mode.
+     *
+     * @return <code>true</code> if in raw mode, <code>false</code>
+     *         otherwise.
+     * @see #setRaw(boolean)
+     */
+    public boolean isRaw() {
+        return raw;
+    }
+
+    /**
+     * Enables or disables raw mode. In raw mode all future entities
+     * (messages or body parts) in the stream will be reported to the
+     * {@link ContentHandler#raw(InputStream)} handler method only.
+     * The stream will contain the entire unparsed entity contents
+     * including header fields and whatever is in the body.
+     *
+     * @param raw <code>true</code> enables raw mode, <code>false</code>
+     *        disables it.
+     */
+    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
+     * will still call
+     * {@link ContentHandler#endMultipart()},
+     * {@link ContentHandler#endBodyPart()},
+     * {@link ContentHandler#endMessage()}, etc to match previous calls
+     * to
+     * {@link ContentHandler#startMultipart(BodyDescriptor)},
+     * {@link ContentHandler#startBodyPart()},
+     * {@link ContentHandler#startMessage()}, etc.
+     */
+    public void stop() {
+        rootStream.truncate();
+    }
+
+    /**
+     * Parses an entity which consists of a header followed by a body containing
+     * arbitrary data, body parts or an embedded message.
+     *
+     * @param is the stream to parse.
+     * @throws IOException on I/O errors.
+     */
+    private void parseEntity(InputStream is) throws IOException {
+        BodyDescriptor bd = parseHeader(is);
+
+        if (bd.isMultipart()) {
+            bodyDescriptors.addFirst(bd);
+
+            handler.startMultipart(bd);
+
+            MimeBoundaryInputStream tempIs =
+                new MimeBoundaryInputStream(is, bd.getBoundary());
+            handler.preamble(new CloseShieldInputStream(tempIs));
+            tempIs.consume();
+
+            while (tempIs.hasMoreParts()) {
+                tempIs = new MimeBoundaryInputStream(is, bd.getBoundary());
+                parseBodyPart(tempIs);
+                tempIs.consume();
+                if (tempIs.parentEOF()) {
+                    prematureEof = true;
+//                    if (log.isWarnEnabled()) {
+//                        log.warn("Line " + rootStream.getLineNumber()
+//                                + ": Body part ended prematurely. "
+//                                + "Higher level boundary detected or "
+//                                + "EOF reached.");
+//                    }
+                    break;
+                }
+            }
+
+            handler.epilogue(new CloseShieldInputStream(is));
+
+            handler.endMultipart();
+
+            bodyDescriptors.removeFirst();
+
+        } else if (bd.isMessage()) {
+            if (bd.isBase64Encoded()) {
+                log.warn("base64 encoded message/rfc822 detected");
+                is = new EOLConvertingInputStream(
+                        new Base64InputStream(is));
+            } else if (bd.isQuotedPrintableEncoded()) {
+                log.warn("quoted-printable encoded message/rfc822 detected");
+                is = new EOLConvertingInputStream(
+                        new QuotedPrintableInputStream(is));
+            }
+            bodyDescriptors.addFirst(bd);
+            parseMessage(is);
+            bodyDescriptors.removeFirst();
+        } else {
+            handler.body(bd, new CloseShieldInputStream(is));
+        }
+
+        /*
+         * Make sure the stream has been consumed.
+         */
+        while (is.read() != -1) {
+        }
+    }
+
+    private void parseMessage(InputStream is) throws IOException {
+        if (raw) {
+            handler.raw(new CloseShieldInputStream(is));
+        } else {
+            handler.startMessage();
+            parseEntity(is);
+            handler.endMessage();
+        }
+    }
+
+    public boolean getPrematureEof() {
+        return prematureEof;
+    }
+
+    private void parseBodyPart(InputStream is) throws IOException {
+        if (raw) {
+            handler.raw(new CloseShieldInputStream(is));
+        } else {
+            handler.startBodyPart();
+            parseEntity(is);
+            handler.endBodyPart();
+        }
+    }
+
+    /**
+     * Parses a header.
+     *
+     * @param is the stream to parse.
+     * @return a <code>BodyDescriptor</code> describing the body following
+     *         the header.
+     */
+    private BodyDescriptor parseHeader(InputStream is) throws IOException {
+        BodyDescriptor bd = new BodyDescriptor(bodyDescriptors.isEmpty()
+                        ? null : (BodyDescriptor) bodyDescriptors.getFirst());
+
+        handler.startHeader();
+
+        int lineNumber = rootStream.getLineNumber();
+
+        StringBuffer sb = new StringBuffer();
+        int curr = 0;
+        int prev = 0;
+        while ((curr = is.read()) != -1) {
+            if (curr == '\n' && (prev == '\n' || prev == 0)) {
+                /*
+                 * [\r]\n[\r]\n or an immediate \r\n have been seen.
+                 */
+                sb.deleteCharAt(sb.length() - 1);
+                break;
+            }
+            sb.append((char) curr);
+            prev = curr == '\r' ? prev : curr;
+        }
+
+//        if (curr == -1 && log.isWarnEnabled()) {
+//            log.warn("Line " + rootStream.getLineNumber()
+//                    + ": Unexpected end of headers detected. "
+//                    + "Boundary detected in header or EOF reached.");
+//        }
+
+        int start = 0;
+        int pos = 0;
+        int startLineNumber = lineNumber;
+        while (pos < sb.length()) {
+            while (pos < sb.length() && sb.charAt(pos) != '\r') {
+                pos++;
+            }
+            if (pos < sb.length() - 1 && sb.charAt(pos + 1) != '\n') {
+                pos++;
+                continue;
+            }
+
+            if (pos >= sb.length() - 2 || fieldChars.get(sb.charAt(pos + 2))) {
+
+                /*
+                 * field should be the complete field data excluding the
+                 * trailing \r\n.
+                 */
+                String field = sb.substring(start, pos);
+                start = pos + 2;
+
+                /*
+                 * Check for a valid field.
+                 */
+                int index = field.indexOf(':');
+                boolean valid = false;
+                if (index != -1 && fieldChars.get(field.charAt(0))) {
+                    valid = true;
+                    String fieldName = field.substring(0, index).trim();
+                    for (int i = 0; i < fieldName.length(); i++) {
+                        if (!fieldChars.get(fieldName.charAt(i))) {
+                            valid = false;
+                            break;
+                        }
+                    }
+
+                    if (valid) {
+                        handler.field(field);
+                        bd.addField(fieldName, field.substring(index + 1));
+                    }
+                }
+
+                if (!valid && log.isWarnEnabled()) {
+                    log.warn("Line " + startLineNumber
+                            + ": Ignoring invalid field: '" + field.trim() + "'");
+                }
+
+                startLineNumber = lineNumber;
+            }
+
+            pos += 2;
+            lineNumber++;
+        }
+
+        handler.endHeader();
+
+        return bd;
+    }
+
+    /**
+     * Sets the <code>ContentHandler</code> to use when reporting
+     * parsing events.
+     *
+     * @param h the <code>ContentHandler</code>.
+     */
+    public void setContentHandler(ContentHandler h) {
+        this.handler = h;
+    }
+
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/RootInputStream.java b/src/org/apache/james/mime4j/RootInputStream.java
new file mode 100644
index 0000000..cc8b241
--- /dev/null
+++ b/src/org/apache/james/mime4j/RootInputStream.java
@@ -0,0 +1,111 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * <code>InputStream</code> used by the parser to wrap the original user
+ * supplied stream. This stream keeps track of the current line number and
+ * can also be truncated. When truncated the stream will appear to have
+ * reached end of file. This is used by the parser's
+ * {@link org.apache.james.mime4j.MimeStreamParser#stop()} method.
+ *
+ *
+ * @version $Id: RootInputStream.java,v 1.2 2004/10/02 12:41:10 ntherning Exp $
+ */
+class RootInputStream extends InputStream {
+    private InputStream is = null;
+    private int lineNumber = 1;
+    private int prev = -1;
+    private boolean truncated = false;
+
+    /**
+     * Creates a new <code>RootInputStream</code>.
+     *
+     * @param in the stream to read from.
+     */
+    public RootInputStream(InputStream is) {
+        this.is = is;
+    }
+
+    /**
+     * Gets the current line number starting at 1
+     * (the number of <code>\r\n</code> read so far plus 1).
+     *
+     * @return the current line number.
+     */
+    public int getLineNumber() {
+        return lineNumber;
+    }
+
+    /**
+     * Truncates this <code>InputStream</code>. After this call any
+     * call to {@link #read()}, {@link #read(byte[]) or
+     * {@link #read(byte[], int, int)} will return
+     * -1 as if end-of-file had been reached.
+     */
+    public void truncate() {
+        this.truncated = true;
+    }
+
+    /**
+     * @see java.io.InputStream#read()
+     */
+    public int read() throws IOException {
+        if (truncated) {
+            return -1;
+        }
+
+        int b = is.read();
+        if (prev == '\r' && b == '\n') {
+            lineNumber++;
+        }
+        prev = b;
+        return b;
+    }
+
+    /**
+     *
+     * @see java.io.InputStream#read(byte[], int, int)
+     */
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (truncated) {
+            return -1;
+        }
+
+        int n = is.read(b, off, len);
+        for (int i = off; i < off + n; i++) {
+            if (prev == '\r' && b[i] == '\n') {
+                lineNumber++;
+            }
+            prev = b[i];
+        }
+        return n;
+    }
+
+    /**
+     * @see java.io.InputStream#read(byte[])
+     */
+    public int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/codec/EncoderUtil.java b/src/org/apache/james/mime4j/codec/EncoderUtil.java
new file mode 100644
index 0000000..6841bc9
--- /dev/null
+++ b/src/org/apache/james/mime4j/codec/EncoderUtil.java
@@ -0,0 +1,630 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.codec;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.BitSet;
+import java.util.Locale;
+
+import org.apache.james.mime4j.util.CharsetUtil;
+
+/**
+ * ANDROID:  THIS CLASS IS COPIED FROM A NEWER VERSION OF MIME4J
+ */
+
+/**
+ * Static methods for encoding header field values. This includes encoded-words
+ * as defined in <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC 2047</a>
+ * or display-names of an e-mail address, for example.
+ * 
+ */
+public class EncoderUtil {
+
+    // This array is a lookup table that translates 6-bit positive integer index
+    // values into their "Base64 Alphabet" equivalents as specified in Table 1
+    // of RFC 2045.
+    // ANDROID:  THIS TABLE IS COPIED FROM BASE64OUTPUTSTREAM
+    static final byte[] BASE64_TABLE = { 'A', 'B', 'C', 'D', 'E', 'F',
+            'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+            'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
+            't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
+            '6', '7', '8', '9', '+', '/' };
+
+    // Byte used to pad output.
+    private static final byte BASE64_PAD = '=';
+
+    private static final BitSet Q_REGULAR_CHARS = initChars("=_?");
+
+    private static final BitSet Q_RESTRICTED_CHARS = initChars("=_?\"#$%&'(),.:;<>@[\\]^`{|}~");
+
+    private static final int MAX_USED_CHARACTERS = 50;
+
+    private static final String ENC_WORD_PREFIX = "=?";
+    private static final String ENC_WORD_SUFFIX = "?=";
+
+    private static final int ENCODED_WORD_MAX_LENGTH = 75; // RFC 2047
+
+    private static final BitSet TOKEN_CHARS = initChars("()<>@,;:\\\"/[]?=");
+
+    private static final BitSet ATEXT_CHARS = initChars("()<>@.,;:\\\"[]");
+
+    private static BitSet initChars(String specials) {
+        BitSet bs = new BitSet(128);
+        for (char ch = 33; ch < 127; ch++) {
+            if (specials.indexOf(ch) == -1) {
+                bs.set(ch);
+            }
+        }
+        return bs;
+    }
+
+    /**
+     * Selects one of the two encodings specified in RFC 2047.
+     */
+    public enum Encoding {
+        /** The B encoding (identical to base64 defined in RFC 2045). */
+        B,
+        /** The Q encoding (similar to quoted-printable defined in RFC 2045). */
+        Q
+    }
+
+    /**
+     * Indicates the intended usage of an encoded word.
+     */
+    public enum Usage {
+        /**
+         * Encoded word is used to replace a 'text' token in any Subject or
+         * Comments header field.
+         */
+        TEXT_TOKEN,
+        /**
+         * Encoded word is used to replace a 'word' entity within a 'phrase',
+         * for example, one that precedes an address in a From, To, or Cc
+         * header.
+         */
+        WORD_ENTITY
+    }
+
+    private EncoderUtil() {
+    }
+
+    /**
+     * Encodes the display-name portion of an address. See <a
+     * href='http://www.faqs.org/rfcs/rfc5322.html'>RFC 5322</a> section 3.4
+     * and <a href='http://www.faqs.org/rfcs/rfc2047.html'>RFC 2047</a> section
+     * 5.3. The specified string should not be folded.
+     * 
+     * @param displayName
+     *            display-name to encode.
+     * @return encoded display-name.
+     */
+    public static String encodeAddressDisplayName(String displayName) {
+        // display-name = phrase
+        // phrase = 1*( encoded-word / word )
+        // word = atom / quoted-string
+        // atom = [CFWS] 1*atext [CFWS]
+        // CFWS = comment or folding white space
+
+        if (isAtomPhrase(displayName)) {
+            return displayName;
+        } else if (hasToBeEncoded(displayName, 0)) {
+            return encodeEncodedWord(displayName, Usage.WORD_ENTITY);
+        } else {
+            return quote(displayName);
+        }
+    }
+
+    /**
+     * Encodes the local part of an address specification as described in RFC
+     * 5322 section 3.4.1. Leading and trailing CFWS should have been removed
+     * before calling this method. The specified string should not contain any
+     * illegal (control or non-ASCII) characters.
+     * 
+     * @param localPart
+     *            the local part to encode
+     * @return the encoded local part.
+     */
+    public static String encodeAddressLocalPart(String localPart) {
+        // local-part = dot-atom / quoted-string
+        // dot-atom = [CFWS] dot-atom-text [CFWS]
+        // CFWS = comment or folding white space
+
+        if (isDotAtomText(localPart)) {
+            return localPart;
+        } else {
+            return quote(localPart);
+        }
+    }
+
+    /**
+     * Encodes the specified strings into a header parameter as described in RFC
+     * 2045 section 5.1 and RFC 2183 section 2. The specified strings should not
+     * contain any illegal (control or non-ASCII) characters.
+     * 
+     * @param name
+     *            parameter name.
+     * @param value
+     *            parameter value.
+     * @return encoded result.
+     */
+    public static String encodeHeaderParameter(String name, String value) {
+        name = name.toLowerCase(Locale.US);
+
+        // value := token / quoted-string
+        if (isToken(value)) {
+            return name + "=" + value;
+        } else {
+            return name + "=" + quote(value);
+        }
+    }
+
+    /**
+     * Shortcut method that encodes the specified text into an encoded-word if
+     * the text has to be encoded.
+     * 
+     * @param text
+     *            text to encode.
+     * @param usage
+     *            whether the encoded-word is to be used to replace a text token
+     *            or a word entity (see RFC 822).
+     * @param usedCharacters
+     *            number of characters already used up (<code>0 <= usedCharacters <= 50</code>).
+     * @return the specified text if encoding is not necessary or an encoded
+     *         word or a sequence of encoded words otherwise.
+     */
+    public static String encodeIfNecessary(String text, Usage usage,
+            int usedCharacters) {
+        if (hasToBeEncoded(text, usedCharacters))
+            return encodeEncodedWord(text, usage, usedCharacters);
+        else
+            return text;
+    }
+
+    /**
+     * Determines if the specified string has to encoded into an encoded-word.
+     * Returns <code>true</code> if the text contains characters that don't
+     * fall into the printable ASCII character set or if the text contains a
+     * 'word' (sequence of non-whitespace characters) longer than 77 characters
+     * (including characters already used up in the line).
+     * 
+     * @param text
+     *            text to analyze.
+     * @param usedCharacters
+     *            number of characters already used up (<code>0 <= usedCharacters <= 50</code>).
+     * @return <code>true</code> if the specified text has to be encoded into
+     *         an encoded-word, <code>false</code> otherwise.
+     */
+    public static boolean hasToBeEncoded(String text, int usedCharacters) {
+        if (text == null)
+            throw new IllegalArgumentException();
+        if (usedCharacters < 0 || usedCharacters > MAX_USED_CHARACTERS)
+            throw new IllegalArgumentException();
+
+        int nonWhiteSpaceCount = usedCharacters;
+
+        for (int idx = 0; idx < text.length(); idx++) {
+            char ch = text.charAt(idx);
+            if (ch == '\t' || ch == ' ') {
+                nonWhiteSpaceCount = 0;
+            } else {
+                nonWhiteSpaceCount++;
+                if (nonWhiteSpaceCount > 77) {
+                    // Line cannot be folded into multiple lines with no more
+                    // than 78 characters each. Encoding as encoded-words makes
+                    // that possible. One character has to be reserved for
+                    // folding white space; that leaves 77 characters.
+                    return true;
+                }
+
+                if (ch < 32 || ch >= 127) {
+                    // non-printable ascii character has to be encoded
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Encodes the specified text into an encoded word or a sequence of encoded
+     * words separated by space. The text is separated into a sequence of
+     * encoded words if it does not fit in a single one.
+     * <p>
+     * The charset to encode the specified text into a byte array and the
+     * encoding to use for the encoded-word are detected automatically.
+     * <p>
+     * This method assumes that zero characters have already been used up in the
+     * current line.
+     * 
+     * @param text
+     *            text to encode.
+     * @param usage
+     *            whether the encoded-word is to be used to replace a text token
+     *            or a word entity (see RFC 822).
+     * @return the encoded word (or sequence of encoded words if the given text
+     *         does not fit in a single encoded word).
+     * @see #hasToBeEncoded(String, int)
+     */
+    public static String encodeEncodedWord(String text, Usage usage) {
+        return encodeEncodedWord(text, usage, 0, null, null);
+    }
+
+    /**
+     * Encodes the specified text into an encoded word or a sequence of encoded
+     * words separated by space. The text is separated into a sequence of
+     * encoded words if it does not fit in a single one.
+     * <p>
+     * The charset to encode the specified text into a byte array and the
+     * encoding to use for the encoded-word are detected automatically.
+     * 
+     * @param text
+     *            text to encode.
+     * @param usage
+     *            whether the encoded-word is to be used to replace a text token
+     *            or a word entity (see RFC 822).
+     * @param usedCharacters
+     *            number of characters already used up (<code>0 <= usedCharacters <= 50</code>).
+     * @return the encoded word (or sequence of encoded words if the given text
+     *         does not fit in a single encoded word).
+     * @see #hasToBeEncoded(String, int)
+     */
+    public static String encodeEncodedWord(String text, Usage usage,
+            int usedCharacters) {
+        return encodeEncodedWord(text, usage, usedCharacters, null, null);
+    }
+
+    /**
+     * Encodes the specified text into an encoded word or a sequence of encoded
+     * words separated by space. The text is separated into a sequence of
+     * encoded words if it does not fit in a single one.
+     * 
+     * @param text
+     *            text to encode.
+     * @param usage
+     *            whether the encoded-word is to be used to replace a text token
+     *            or a word entity (see RFC 822).
+     * @param usedCharacters
+     *            number of characters already used up (<code>0 <= usedCharacters <= 50</code>).
+     * @param charset
+     *            the Java charset that should be used to encode the specified
+     *            string into a byte array. A suitable charset is detected
+     *            automatically if this parameter is <code>null</code>.
+     * @param encoding
+     *            the encoding to use for the encoded-word (either B or Q). A
+     *            suitable encoding is automatically chosen if this parameter is
+     *            <code>null</code>.
+     * @return the encoded word (or sequence of encoded words if the given text
+     *         does not fit in a single encoded word).
+     * @see #hasToBeEncoded(String, int)
+     */
+    public static String encodeEncodedWord(String text, Usage usage,
+            int usedCharacters, Charset charset, Encoding encoding) {
+        if (text == null)
+            throw new IllegalArgumentException();
+        if (usedCharacters < 0 || usedCharacters > MAX_USED_CHARACTERS)
+            throw new IllegalArgumentException();
+
+        if (charset == null)
+            charset = determineCharset(text);
+
+        String mimeCharset = CharsetUtil.toMimeCharset(charset.name());
+        if (mimeCharset == null) {
+            // cannot happen if charset was originally null
+            throw new IllegalArgumentException("Unsupported charset");
+        }
+
+        byte[] bytes = encode(text, charset);
+
+        if (encoding == null)
+            encoding = determineEncoding(bytes, usage);
+
+        if (encoding == Encoding.B) {
+            String prefix = ENC_WORD_PREFIX + mimeCharset + "?B?";
+            return encodeB(prefix, text, usedCharacters, charset, bytes);
+        } else {
+            String prefix = ENC_WORD_PREFIX + mimeCharset + "?Q?";
+            return encodeQ(prefix, text, usage, usedCharacters, charset, bytes);
+        }
+    }
+
+    /**
+     * Encodes the specified byte array using the B encoding defined in RFC
+     * 2047.
+     * 
+     * @param bytes
+     *            byte array to encode.
+     * @return encoded string.
+     */
+    public static String encodeB(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+
+        int idx = 0;
+        final int end = bytes.length;
+        for (; idx < end - 2; idx += 3) {
+            int data = (bytes[idx] & 0xff) << 16 | (bytes[idx + 1] & 0xff) << 8
+                    | bytes[idx + 2] & 0xff;
+            sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]);
+            sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]);
+            sb.append((char) BASE64_TABLE[data >> 6 & 0x3f]);
+            sb.append((char) BASE64_TABLE[data & 0x3f]);
+        }
+
+        if (idx == end - 2) {
+            int data = (bytes[idx] & 0xff) << 16 | (bytes[idx + 1] & 0xff) << 8;
+            sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]);
+            sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]);
+            sb.append((char) BASE64_TABLE[data >> 6 & 0x3f]);
+            sb.append((char) BASE64_PAD);
+
+        } else if (idx == end - 1) {
+            int data = (bytes[idx] & 0xff) << 16;
+            sb.append((char) BASE64_TABLE[data >> 18 & 0x3f]);
+            sb.append((char) BASE64_TABLE[data >> 12 & 0x3f]);
+            sb.append((char) BASE64_PAD);
+            sb.append((char) BASE64_PAD);
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Encodes the specified byte array using the Q encoding defined in RFC
+     * 2047.
+     * 
+     * @param bytes
+     *            byte array to encode.
+     * @param usage
+     *            whether the encoded-word is to be used to replace a text token
+     *            or a word entity (see RFC 822).
+     * @return encoded string.
+     */
+    public static String encodeQ(byte[] bytes, Usage usage) {
+        BitSet qChars = usage == Usage.TEXT_TOKEN ? Q_REGULAR_CHARS
+                : Q_RESTRICTED_CHARS;
+
+        StringBuilder sb = new StringBuilder();
+
+        final int end = bytes.length;
+        for (int idx = 0; idx < end; idx++) {
+            int v = bytes[idx] & 0xff;
+            if (v == 32) {
+                sb.append('_');
+            } else if (!qChars.get(v)) {
+                sb.append('=');
+                sb.append(hexDigit(v >>> 4));
+                sb.append(hexDigit(v & 0xf));
+            } else {
+                sb.append((char) v);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Tests whether the specified string is a token as defined in RFC 2045
+     * section 5.1.
+     * 
+     * @param str
+     *            string to test.
+     * @return <code>true</code> if the specified string is a RFC 2045 token,
+     *         <code>false</code> otherwise.
+     */
+    public static boolean isToken(String str) {
+        // token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, or tspecials>
+        // tspecials := "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" /
+        // <"> / "/" / "[" / "]" / "?" / "="
+        // CTL := 0.- 31., 127.
+
+        final int length = str.length();
+        if (length == 0)
+            return false;
+
+        for (int idx = 0; idx < length; idx++) {
+            char ch = str.charAt(idx);
+            if (!TOKEN_CHARS.get(ch))
+                return false;
+        }
+
+        return true;
+    }
+
+    private static boolean isAtomPhrase(String str) {
+        // atom = [CFWS] 1*atext [CFWS]
+
+        boolean containsAText = false;
+
+        final int length = str.length();
+        for (int idx = 0; idx < length; idx++) {
+            char ch = str.charAt(idx);
+            if (ATEXT_CHARS.get(ch)) {
+                containsAText = true;
+            } else if (!CharsetUtil.isWhitespace(ch)) {
+                return false;
+            }
+        }
+
+        return containsAText;
+    }
+
+    // RFC 5322 section 3.2.3
+    private static boolean isDotAtomText(String str) {
+        // dot-atom-text = 1*atext *("." 1*atext)
+        // atext = ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" /
+        // "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"
+
+        char prev = '.';
+
+        final int length = str.length();
+        if (length == 0)
+            return false;
+
+        for (int idx = 0; idx < length; idx++) {
+            char ch = str.charAt(idx);
+
+            if (ch == '.') {
+                if (prev == '.' || idx == length - 1)
+                    return false;
+            } else {
+                if (!ATEXT_CHARS.get(ch))
+                    return false;
+            }
+
+            prev = ch;
+        }
+
+        return true;
+    }
+
+    // RFC 5322 section 3.2.4
+    private static String quote(String str) {
+        // quoted-string = [CFWS] DQUOTE *([FWS] qcontent) [FWS] DQUOTE [CFWS]
+        // qcontent = qtext / quoted-pair
+        // qtext = %d33 / %d35-91 / %d93-126
+        // quoted-pair = ("\" (VCHAR / WSP))
+        // VCHAR = %x21-7E
+        // DQUOTE = %x22
+
+        String escaped = str.replaceAll("[\\\\\"]", "\\\\$0");
+        return "\"" + escaped + "\"";
+    }
+
+    private static String encodeB(String prefix, String text,
+            int usedCharacters, Charset charset, byte[] bytes) {
+        int encodedLength = bEncodedLength(bytes);
+
+        int totalLength = prefix.length() + encodedLength
+                + ENC_WORD_SUFFIX.length();
+        if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) {
+            return prefix + encodeB(bytes) + ENC_WORD_SUFFIX;
+        } else {
+            int splitOffset = text.offsetByCodePoints(text.length() / 2, -1);
+                                                         
+            String part1 = text.substring(0, splitOffset);
+            byte[] bytes1 = encode(part1, charset);
+            String word1 = encodeB(prefix, part1, usedCharacters, charset,
+                    bytes1);
+
+            String part2 = text.substring(splitOffset);
+            byte[] bytes2 = encode(part2, charset);
+            String word2 = encodeB(prefix, part2, 0, charset, bytes2);
+
+            return word1 + " " + word2;
+        }
+    }
+
+    private static int bEncodedLength(byte[] bytes) {
+        return (bytes.length + 2) / 3 * 4;
+    }
+
+    private static String encodeQ(String prefix, String text, Usage usage,
+            int usedCharacters, Charset charset, byte[] bytes) {
+        int encodedLength = qEncodedLength(bytes, usage);
+
+        int totalLength = prefix.length() + encodedLength
+                + ENC_WORD_SUFFIX.length();
+        if (totalLength <= ENCODED_WORD_MAX_LENGTH - usedCharacters) {
+            return prefix + encodeQ(bytes, usage) + ENC_WORD_SUFFIX;
+        } else {
+            int splitOffset = text.offsetByCodePoints(text.length() / 2, -1);
+
+            String part1 = text.substring(0, splitOffset);
+            byte[] bytes1 = encode(part1, charset);
+            String word1 = encodeQ(prefix, part1, usage, usedCharacters,
+                    charset, bytes1);
+
+            String part2 = text.substring(splitOffset);
+            byte[] bytes2 = encode(part2, charset);
+            String word2 = encodeQ(prefix, part2, usage, 0, charset, bytes2);
+
+            return word1 + " " + word2;
+        }
+    }
+
+    private static int qEncodedLength(byte[] bytes, Usage usage) {
+        BitSet qChars = usage == Usage.TEXT_TOKEN ? Q_REGULAR_CHARS
+                : Q_RESTRICTED_CHARS;
+
+        int count = 0;
+
+        for (int idx = 0; idx < bytes.length; idx++) {
+            int v = bytes[idx] & 0xff;
+            if (v == 32) {
+                count++;
+            } else if (!qChars.get(v)) {
+                count += 3;
+            } else {
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    private static byte[] encode(String text, Charset charset) {
+        ByteBuffer buffer = charset.encode(text);
+        byte[] bytes = new byte[buffer.limit()];
+        buffer.get(bytes);
+        return bytes;
+    }
+
+    private static Charset determineCharset(String text) {
+        // it is an important property of iso-8859-1 that it directly maps
+        // unicode code points 0000 to 00ff to byte values 00 to ff.
+        boolean ascii = true;
+        final int len = text.length();
+        for (int index = 0; index < len; index++) {
+            char ch = text.charAt(index);
+            if (ch > 0xff) {
+                return CharsetUtil.UTF_8;
+            }
+            if (ch > 0x7f) {
+                ascii = false;
+            }
+        }
+        return ascii ? CharsetUtil.US_ASCII : CharsetUtil.ISO_8859_1;
+    }
+
+    private static Encoding determineEncoding(byte[] bytes, Usage usage) {
+        if (bytes.length == 0)
+            return Encoding.Q;
+
+        BitSet qChars = usage == Usage.TEXT_TOKEN ? Q_REGULAR_CHARS
+                : Q_RESTRICTED_CHARS;
+
+        int qEncoded = 0;
+        for (int i = 0; i < bytes.length; i++) {
+            int v = bytes[i] & 0xff;
+            if (v != 32 && !qChars.get(v)) {
+                qEncoded++;
+            }
+        }
+
+        int percentage = qEncoded * 100 / bytes.length;
+        return percentage > 30 ? Encoding.B : Encoding.Q;
+    }
+
+    private static char hexDigit(int i) {
+        return i < 10 ? (char) (i + '0') : (char) (i - 10 + 'A');
+    }
+}
diff --git a/src/org/apache/james/mime4j/decoder/Base64InputStream.java b/src/org/apache/james/mime4j/decoder/Base64InputStream.java
new file mode 100644
index 0000000..77f5d7d
--- /dev/null
+++ b/src/org/apache/james/mime4j/decoder/Base64InputStream.java
@@ -0,0 +1,151 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+/**
+ * Modified to improve efficiency by Android   21-Aug-2009
+ */
+
+package org.apache.james.mime4j.decoder;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Performs Base-64 decoding on an underlying stream.
+ * 
+ * 
+ * @version $Id: Base64InputStream.java,v 1.3 2004/11/29 13:15:47 ntherning Exp $
+ */
+public class Base64InputStream extends InputStream {
+    private final InputStream s;
+    private int outCount = 0;
+    private int outIndex = 0;
+    private final int[] outputBuffer = new int[3];
+    private final byte[] inputBuffer = new byte[4];
+    private boolean done = false;
+
+    public Base64InputStream(InputStream s) {
+        this.s = s;
+    }
+
+    /**
+     * Closes the underlying stream.
+     * 
+     * @throws IOException on I/O errors.
+     */
+    @Override
+    public void close() throws IOException {
+        s.close();
+    }
+    
+    @Override
+    public int read() throws IOException {
+        if (outIndex == outCount) {
+            fillBuffer();
+            if (outIndex == outCount) {
+                return -1;
+            }
+        }
+
+        return outputBuffer[outIndex++];
+    }
+
+    /**
+     * Retrieve data from the underlying stream, decode it,
+     * and put the results in the byteq.
+     * @throws IOException
+     */
+    private void fillBuffer() throws IOException {
+        outCount = 0;
+        outIndex = 0;
+        int inCount = 0;
+
+        int i;
+        // "done" is needed for the two successive '=' at the end
+        while (!done) {
+            switch (i = s.read()) {
+                case -1:
+                    // No more input - just return, let outputBuffer drain out, and be done
+                    return;
+                case '=':
+                    // once we meet the first '=', avoid reading the second '='
+                    done = true;
+                    decodeAndEnqueue(inCount);
+                    return;
+                default:
+                    byte sX = TRANSLATION[i];
+                    if (sX < 0) continue;
+                    inputBuffer[inCount++] = sX;
+                    if (inCount == 4) {
+                        decodeAndEnqueue(inCount);
+                        return;
+                    }
+                    break;
+            }
+        }
+    }
+
+    private void decodeAndEnqueue(int len) {
+        int accum = 0;
+        accum |= inputBuffer[0] << 18;
+        accum |= inputBuffer[1] << 12;
+        accum |= inputBuffer[2] << 6;
+        accum |= inputBuffer[3];
+
+        // There's a bit of duplicated code here because we want to have straight-through operation
+        // for the most common case of len==4
+        if (len == 4) {
+            outputBuffer[0] = (accum >> 16) & 0xFF;
+            outputBuffer[1] = (accum >> 8) & 0xFF;
+            outputBuffer[2] = (accum) & 0xFF;
+            outCount = 3;
+            return;
+        } else if (len == 3) {
+            outputBuffer[0] = (accum >> 16) & 0xFF;
+            outputBuffer[1] = (accum >> 8) & 0xFF;
+            outCount = 2;
+            return;
+        } else {    // len == 2
+            outputBuffer[0] = (accum >> 16) & 0xFF;
+            outCount = 1;
+            return;
+        }
+    }
+
+    private static byte[] TRANSLATION = {
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 0x20 */
+        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 0x30 */
+        -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 0x40 */
+        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 0x50 */
+        -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60 */
+        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 0x70 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xA0 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xB0 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xC0 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xD0 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xE0 */
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1     /* 0xF0 */
+    };
+
+
+}
diff --git a/src/org/apache/james/mime4j/decoder/ByteQueue.java b/src/org/apache/james/mime4j/decoder/ByteQueue.java
new file mode 100644
index 0000000..6d7ccef
--- /dev/null
+++ b/src/org/apache/james/mime4j/decoder/ByteQueue.java
@@ -0,0 +1,62 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.decoder;
+
+import java.util.Iterator;
+
+public class ByteQueue {
+
+    private UnboundedFifoByteBuffer buf;
+    private int initialCapacity = -1;
+
+    public ByteQueue() {
+        buf = new UnboundedFifoByteBuffer();
+    }
+
+    public ByteQueue(int initialCapacity) {
+        buf = new UnboundedFifoByteBuffer(initialCapacity);
+        this.initialCapacity = initialCapacity;
+    }
+
+    public void enqueue(byte b) {
+        buf.add(b);
+    }
+
+    public byte dequeue() {
+        return buf.remove();
+    }
+
+    public int count() {
+        return buf.size();
+    }
+
+    public void clear() {
+        if (initialCapacity != -1)
+            buf = new UnboundedFifoByteBuffer(initialCapacity);
+        else
+            buf = new UnboundedFifoByteBuffer();
+    }
+
+    public Iterator iterator() {
+        return buf.iterator();
+    }
+
+
+}
diff --git a/src/org/apache/james/mime4j/decoder/DecoderUtil.java b/src/org/apache/james/mime4j/decoder/DecoderUtil.java
new file mode 100644
index 0000000..48fe07d
--- /dev/null
+++ b/src/org/apache/james/mime4j/decoder/DecoderUtil.java
@@ -0,0 +1,284 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.decoder;
+
+//BEGIN android-changed: Stubbing out logging
+import org.apache.james.mime4j.Log;
+import org.apache.james.mime4j.LogFactory;
+//END android-changed
+import org.apache.james.mime4j.util.CharsetUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Static methods for decoding strings, byte arrays and encoded words.
+ *
+ * 
+ * @version $Id: DecoderUtil.java,v 1.3 2005/02/07 15:33:59 ntherning Exp $
+ */
+public class DecoderUtil {
+    private static Log log = LogFactory.getLog(DecoderUtil.class);
+    
+    /**
+     * Decodes a string containing quoted-printable encoded data. 
+     * 
+     * @param s the string to decode.
+     * @return the decoded bytes.
+     */
+    public static byte[] decodeBaseQuotedPrintable(String s) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        
+        try {
+            byte[] bytes = s.getBytes("US-ASCII");
+            
+            QuotedPrintableInputStream is = new QuotedPrintableInputStream(
+                                               new ByteArrayInputStream(bytes));
+            
+            int b = 0;
+            while ((b = is.read()) != -1) {
+                baos.write(b);
+            }
+        } catch (IOException e) {
+            /*
+             * This should never happen!
+             */
+            log.error(e);
+        }
+        
+        return baos.toByteArray();
+    }
+    
+    /**
+     * Decodes a string containing base64 encoded data. 
+     * 
+     * @param s the string to decode.
+     * @return the decoded bytes.
+     */
+    public static byte[] decodeBase64(String s) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        
+        try {
+            byte[] bytes = s.getBytes("US-ASCII");
+            
+            Base64InputStream is = new Base64InputStream(
+                                        new ByteArrayInputStream(bytes));
+            
+            int b = 0;
+            while ((b = is.read()) != -1) {
+                baos.write(b);
+            }
+        } catch (IOException e) {
+            /*
+             * This should never happen!
+             */
+            log.error(e);
+        }
+        
+        return baos.toByteArray();
+    }
+    
+    /**
+     * Decodes an encoded word encoded with the 'B' encoding (described in 
+     * RFC 2047) found in a header field body.
+     * 
+     * @param encodedWord the encoded word to decode.
+     * @param charset the Java charset to use.
+     * @return the decoded string.
+     * @throws UnsupportedEncodingException if the given Java charset isn't 
+     *         supported.
+     */
+    public static String decodeB(String encodedWord, String charset) 
+            throws UnsupportedEncodingException {
+        
+        return new String(decodeBase64(encodedWord), charset);
+    }
+    
+    /**
+     * Decodes an encoded word encoded with the 'Q' encoding (described in 
+     * RFC 2047) found in a header field body.
+     * 
+     * @param encodedWord the encoded word to decode.
+     * @param charset the Java charset to use.
+     * @return the decoded string.
+     * @throws UnsupportedEncodingException if the given Java charset isn't 
+     *         supported.
+     */
+    public static String decodeQ(String encodedWord, String charset)
+            throws UnsupportedEncodingException {
+           
+        /*
+         * Replace _ with =20
+         */
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < encodedWord.length(); i++) {
+            char c = encodedWord.charAt(i);
+            if (c == '_') {
+                sb.append("=20");
+            } else {
+                sb.append(c);
+            }
+        }
+        
+        return new String(decodeBaseQuotedPrintable(sb.toString()), charset);
+    }
+    
+    /**
+     * Decodes a string containing encoded words as defined by RFC 2047.
+     * Encoded words in have the form 
+     * =?charset?enc?Encoded word?= where enc is either 'Q' or 'q' for 
+     * quoted-printable and 'B' or 'b' for Base64.
+     * 
+     * ANDROID:  COPIED FROM A NEWER VERSION OF MIME4J
+     * 
+     * @param body the string to decode.
+     * @return the decoded string.
+     */
+    public static String decodeEncodedWords(String body) {
+        
+        // ANDROID:  Most strings will not include "=?" so a quick test can prevent unneeded
+        // object creation.  This could also be handled via lazy creation of the StringBuilder.
+        if (body.indexOf("=?") == -1) {
+            return body;
+        }
+
+        int previousEnd = 0;
+        boolean previousWasEncoded = false;
+
+        StringBuilder sb = new StringBuilder();
+
+        while (true) {
+            int begin = body.indexOf("=?", previousEnd);
+
+            // ANDROID:  The mime4j original version has an error here.  It gets confused if
+            // the encoded string begins with an '=' (just after "?Q?").  This patch seeks forward
+            // to find the two '?' in the "header", before looking for the final "?=".
+            if (begin == -1) {
+                break;
+            }
+            int qm1 = body.indexOf('?', begin + 2);
+            if (qm1 == -1) {
+                break;
+            }
+            int qm2 = body.indexOf('?', qm1 + 1);
+            if (qm2 == -1) {
+                break;
+            }
+            int end = body.indexOf("?=", qm2 + 1);
+            if (end == -1) {
+                break;
+            }
+            end += 2;
+
+            String sep = body.substring(previousEnd, begin);
+
+            String decoded = decodeEncodedWord(body, begin, end);
+            if (decoded == null) {
+                sb.append(sep);
+                sb.append(body.substring(begin, end));
+            } else {
+                if (!previousWasEncoded || !CharsetUtil.isWhitespace(sep)) {
+                    sb.append(sep);
+                }
+                sb.append(decoded);
+            }
+
+            previousEnd = end;
+            previousWasEncoded = decoded != null;
+        }
+
+        if (previousEnd == 0)
+            return body;
+
+        sb.append(body.substring(previousEnd));
+        return sb.toString();
+    }
+
+    // return null on error. Begin is index of '=?' in body.
+    public static String decodeEncodedWord(String body, int begin, int end) {
+        // Skip the '?=' chars in body and scan forward from there for next '?'
+        int qm1 = body.indexOf('?', begin + 2);
+        if (qm1 == -1 || qm1 == end - 2)
+            return null;
+
+        int qm2 = body.indexOf('?', qm1 + 1);
+        if (qm2 == -1 || qm2 == end - 2)
+            return null;
+
+        String mimeCharset = body.substring(begin + 2, qm1);
+        String encoding = body.substring(qm1 + 1, qm2);
+        String encodedText = body.substring(qm2 + 1, end - 2);
+
+        String charset = CharsetUtil.toJavaCharset(mimeCharset);
+        if (charset == null) {
+            if (log.isWarnEnabled()) {
+                log.warn("MIME charset '" + mimeCharset + "' in encoded word '"
+                        + body.substring(begin, end) + "' doesn't have a "
+                        + "corresponding Java charset");
+            }
+            return null;
+        } else if (!CharsetUtil.isDecodingSupported(charset)) {
+            if (log.isWarnEnabled()) {
+                log.warn("Current JDK doesn't support decoding of charset '"
+                        + charset + "' (MIME charset '" + mimeCharset
+                        + "' in encoded word '" + body.substring(begin, end)
+                        + "')");
+            }
+            return null;
+        }
+
+        if (encodedText.length() == 0) {
+            if (log.isWarnEnabled()) {
+                log.warn("Missing encoded text in encoded word: '"
+                        + body.substring(begin, end) + "'");
+            }
+            return null;
+        }
+
+        try {
+            if (encoding.equalsIgnoreCase("Q")) {
+                return DecoderUtil.decodeQ(encodedText, charset);
+            } else if (encoding.equalsIgnoreCase("B")) {
+                return DecoderUtil.decodeB(encodedText, charset);
+            } else {
+                if (log.isWarnEnabled()) {
+                    log.warn("Warning: Unknown encoding in encoded word '"
+                            + body.substring(begin, end) + "'");
+                }
+                return null;
+            }
+        } catch (UnsupportedEncodingException e) {
+            // should not happen because of isDecodingSupported check above
+            if (log.isWarnEnabled()) {
+                log.warn("Unsupported encoding in encoded word '"
+                        + body.substring(begin, end) + "'", e);
+            }
+            return null;
+        } catch (RuntimeException e) {
+            if (log.isWarnEnabled()) {
+                log.warn("Could not decode encoded word '"
+                        + body.substring(begin, end) + "'", e);
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java b/src/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java
new file mode 100644
index 0000000..e43f398
--- /dev/null
+++ b/src/org/apache/james/mime4j/decoder/QuotedPrintableInputStream.java
@@ -0,0 +1,229 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.decoder;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+//BEGIN android-changed: Stubbing out logging
+import org.apache.james.mime4j.Log;
+import org.apache.james.mime4j.LogFactory;
+//END android-changed
+
+/**
+ * Performs Quoted-Printable decoding on an underlying stream.
+ * 
+ * 
+ * 
+ * @version $Id: QuotedPrintableInputStream.java,v 1.3 2004/11/29 13:15:47 ntherning Exp $
+ */
+public class QuotedPrintableInputStream extends InputStream {
+    private static Log log = LogFactory.getLog(QuotedPrintableInputStream.class);
+    
+    private InputStream stream;
+    ByteQueue byteq = new ByteQueue();
+    ByteQueue pushbackq = new ByteQueue();
+    private byte state = 0;
+
+    public QuotedPrintableInputStream(InputStream stream) {
+        this.stream = stream;
+    }
+    
+    /**
+     * Closes the underlying stream.
+     * 
+     * @throws IOException on I/O errors.
+     */
+    public void close() throws IOException {
+        stream.close();
+    }
+
+    public int read() throws IOException {
+        fillBuffer();
+        if (byteq.count() == 0)
+            return -1;
+        else {
+            byte val = byteq.dequeue();
+            if (val >= 0)
+                return val;
+            else
+                return val & 0xFF;
+        }
+    }
+
+    /**
+     * Pulls bytes out of the underlying stream and places them in the
+     * pushback queue.  This is necessary (vs. reading from the
+     * underlying stream directly) to detect and filter out "transport
+     * padding" whitespace, i.e., all whitespace that appears immediately
+     * before a CRLF.
+     *
+     * @throws IOException Underlying stream threw IOException.
+     */
+    private void populatePushbackQueue() throws IOException {
+        //Debug.verify(pushbackq.count() == 0, "PopulatePushbackQueue called when pushback queue was not empty!");
+
+        if (pushbackq.count() != 0)
+            return;
+
+        while (true) {
+            int i = stream.read();
+            switch (i) {
+                case -1:
+                    // stream is done
+                    pushbackq.clear();  // discard any whitespace preceding EOF
+                    return;
+                case ' ':
+                case '\t':
+                    pushbackq.enqueue((byte)i);
+                    break;
+                case '\r':
+                case '\n':
+                    pushbackq.clear();  // discard any whitespace preceding EOL
+                    pushbackq.enqueue((byte)i);
+                    return;
+                default:
+                    pushbackq.enqueue((byte)i);
+                    return;
+            }
+        }
+    }
+
+    /**
+     * Causes the pushback queue to get populated if it is empty, then
+     * consumes and decodes bytes out of it until one or more bytes are
+     * in the byte queue.  This decoding step performs the actual QP
+     * decoding.
+     *
+     * @throws IOException Underlying stream threw IOException.
+     */
+    private void fillBuffer() throws IOException {
+        byte msdChar = 0;  // first digit of escaped num
+        while (byteq.count() == 0) {
+            if (pushbackq.count() == 0) {
+                populatePushbackQueue();
+                if (pushbackq.count() == 0)
+                    return;
+            }
+
+            byte b = (byte)pushbackq.dequeue();
+
+            switch (state) {
+                case 0:  // start state, no bytes pending
+                    if (b != '=') {
+                        byteq.enqueue(b);
+                        break;  // state remains 0
+                    } else {
+                        state = 1;
+                        break;
+                    }
+                case 1:  // encountered "=" so far
+                    if (b == '\r') {
+                        state = 2;
+                        break;
+                    } else if ((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')) {
+                        state = 3;
+                        msdChar = b;  // save until next digit encountered
+                        break;
+                    } else if (b == '=') {
+                        /*
+                         * Special case when == is encountered.
+                         * Emit one = and stay in this state.
+                         */
+                        if (log.isWarnEnabled()) {
+                            log.warn("Malformed MIME; got ==");
+                        }
+                        byteq.enqueue((byte)'=');
+                        break;
+                    } else {
+                        if (log.isWarnEnabled()) {
+                            log.warn("Malformed MIME; expected \\r or "
+                                    + "[0-9A-Z], got " + b);
+                        }
+                        state = 0;
+                        byteq.enqueue((byte)'=');
+                        byteq.enqueue(b);
+                        break;
+                    }
+                case 2:  // encountered "=\r" so far
+                    if (b == '\n') {
+                        state = 0;
+                        break;
+                    } else {
+                        if (log.isWarnEnabled()) {
+                            log.warn("Malformed MIME; expected " 
+                                    + (int)'\n' + ", got " + b);
+                        }
+                        state = 0;
+                        byteq.enqueue((byte)'=');
+                        byteq.enqueue((byte)'\r');
+                        byteq.enqueue(b);
+                        break;
+                    }
+                case 3:  // encountered =<digit> so far; expecting another <digit> to complete the octet
+                    if ((b >= '0' && b <= '9') || (b >= 'A' && b <= 'F') || (b >= 'a' && b <= 'f')) {
+                        byte msd = asciiCharToNumericValue(msdChar);
+                        byte low = asciiCharToNumericValue(b);
+                        state = 0;
+                        byteq.enqueue((byte)((msd << 4) | low));
+                        break;
+                    } else {
+                        if (log.isWarnEnabled()) {
+                            log.warn("Malformed MIME; expected "
+                                     + "[0-9A-Z], got " + b);
+                        }
+                        state = 0;
+                        byteq.enqueue((byte)'=');
+                        byteq.enqueue(msdChar);
+                        byteq.enqueue(b);
+                        break;
+                    }
+                default:  // should never happen
+                    log.error("Illegal state: " + state);
+                    state = 0;
+                    byteq.enqueue(b);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Converts '0' => 0, 'A' => 10, etc.
+     * @param c ASCII character value.
+     * @return Numeric value of hexadecimal character.
+     */
+    private byte asciiCharToNumericValue(byte c) {
+        if (c >= '0' && c <= '9') {
+            return (byte)(c - '0');
+        } else if (c >= 'A' && c <= 'Z') {
+            return (byte)(0xA + (c - 'A'));
+        } else if (c >= 'a' && c <= 'z') {
+            return (byte)(0xA + (c - 'a'));
+        } else {
+            /*
+             * This should never happen since all calls to this method
+             * are preceded by a check that c is in [0-9A-Za-z]
+             */
+            throw new IllegalArgumentException((char) c 
+                    + " is not a hexadecimal digit");
+        }
+    }
+
+}
diff --git a/src/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java b/src/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java
new file mode 100644
index 0000000..f01194f
--- /dev/null
+++ b/src/org/apache/james/mime4j/decoder/UnboundedFifoByteBuffer.java
@@ -0,0 +1,272 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.decoder;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * UnboundedFifoByteBuffer is a very efficient buffer implementation.
+ * According to performance testing, it exhibits a constant access time, but it
+ * also outperforms ArrayList when used for the same purpose.
+ * <p>
+ * The removal order of an <code>UnboundedFifoByteBuffer</code> is based on the insertion
+ * order; elements are removed in the same order in which they were added.
+ * The iteration order is the same as the removal order.
+ * <p>
+ * The {@link #remove()} and {@link #get()} operations perform in constant time.
+ * The {@link #add(Object)} operation performs in amortized constant time.  All
+ * other operations perform in linear time or worse.
+ * <p>
+ * Note that this implementation is not synchronized.  The following can be
+ * used to provide synchronized access to your <code>UnboundedFifoByteBuffer</code>:
+ * <pre>
+ *   Buffer fifo = BufferUtils.synchronizedBuffer(new UnboundedFifoByteBuffer());
+ * </pre>
+ * <p>
+ * This buffer prevents null objects from being added.
+ *
+ * @since Commons Collections 3.0 (previously in main package v2.1)
+ * @version $Revision: 1.1 $ $Date: 2004/08/24 06:52:02 $
+ *
+ * 
+ * 
+ * 
+ * 
+ * 
+ */
+class UnboundedFifoByteBuffer {
+
+    protected byte[] buffer;
+    protected int head;
+    protected int tail;
+
+    /**
+     * Constructs an UnboundedFifoByteBuffer with the default number of elements.
+     * It is exactly the same as performing the following:
+     *
+     * <pre>
+     *   new UnboundedFifoByteBuffer(32);
+     * </pre>
+     */
+    public UnboundedFifoByteBuffer() {
+        this(32);
+    }
+
+    /**
+     * Constructs an UnboundedFifoByteBuffer with the specified number of elements.
+     * The integer must be a positive integer.
+     *
+     * @param initialSize  the initial size of the buffer
+     * @throws IllegalArgumentException  if the size is less than 1
+     */
+    public UnboundedFifoByteBuffer(int initialSize) {
+        if (initialSize <= 0) {
+            throw new IllegalArgumentException("The size must be greater than 0");
+        }
+        buffer = new byte[initialSize + 1];
+        head = 0;
+        tail = 0;
+    }
+
+    /**
+     * Returns the number of elements stored in the buffer.
+     *
+     * @return this buffer's size
+     */
+    public int size() {
+        int size = 0;
+
+        if (tail < head) {
+            size = buffer.length - head + tail;
+        } else {
+            size = tail - head;
+        }
+
+        return size;
+    }
+
+    /**
+     * Returns true if this buffer is empty; false otherwise.
+     *
+     * @return true if this buffer is empty
+     */
+    public boolean isEmpty() {
+        return (size() == 0);
+    }
+
+    /**
+     * Adds the given element to this buffer.
+     *
+     * @param b  the byte to add
+     * @return true, always
+     */
+    public boolean add(final byte b) {
+
+        if (size() + 1 >= buffer.length) {
+            byte[] tmp = new byte[((buffer.length - 1) * 2) + 1];
+
+            int j = 0;
+            for (int i = head; i != tail;) {
+                tmp[j] = buffer[i];
+                buffer[i] = 0;
+
+                j++;
+                i++;
+                if (i == buffer.length) {
+                    i = 0;
+                }
+            }
+
+            buffer = tmp;
+            head = 0;
+            tail = j;
+        }
+
+        buffer[tail] = b;
+        tail++;
+        if (tail >= buffer.length) {
+            tail = 0;
+        }
+        return true;
+    }
+
+    /**
+     * Returns the next object in the buffer.
+     *
+     * @return the next object in the buffer
+     * @throws BufferUnderflowException  if this buffer is empty
+     */
+    public byte get() {
+        if (isEmpty()) {
+            throw new IllegalStateException("The buffer is already empty");
+        }
+
+        return buffer[head];
+    }
+
+    /**
+     * Removes the next object from the buffer
+     *
+     * @return the removed object
+     * @throws BufferUnderflowException  if this buffer is empty
+     */
+    public byte remove() {
+        if (isEmpty()) {
+            throw new IllegalStateException("The buffer is already empty");
+        }
+
+        byte element = buffer[head];
+
+        head++;
+        if (head >= buffer.length) {
+            head = 0;
+        }
+
+        return element;
+    }
+
+    /**
+     * Increments the internal index.
+     *
+     * @param index  the index to increment
+     * @return the updated index
+     */
+    private int increment(int index) {
+        index++;
+        if (index >= buffer.length) {
+            index = 0;
+        }
+        return index;
+    }
+
+    /**
+     * Decrements the internal index.
+     *
+     * @param index  the index to decrement
+     * @return the updated index
+     */
+    private int decrement(int index) {
+        index--;
+        if (index < 0) {
+            index = buffer.length - 1;
+        }
+        return index;
+    }
+
+    /**
+     * Returns an iterator over this buffer's elements.
+     *
+     * @return an iterator over this buffer's elements
+     */
+    public Iterator iterator() {
+        return new Iterator() {
+
+            private int index = head;
+            private int lastReturnedIndex = -1;
+
+            public boolean hasNext() {
+                return index != tail;
+
+            }
+
+            public Object next() {
+                if (!hasNext()) {
+                    throw new NoSuchElementException();
+                }
+                lastReturnedIndex = index;
+                index = increment(index);
+                return new Byte(buffer[lastReturnedIndex]);
+            }
+
+            public void remove() {
+                if (lastReturnedIndex == -1) {
+                    throw new IllegalStateException();
+                }
+
+                // First element can be removed quickly
+                if (lastReturnedIndex == head) {
+                    UnboundedFifoByteBuffer.this.remove();
+                    lastReturnedIndex = -1;
+                    return;
+                }
+
+                // Other elements require us to shift the subsequent elements
+                int i = lastReturnedIndex + 1;
+                while (i != tail) {
+                    if (i >= buffer.length) {
+                        buffer[i - 1] = buffer[0];
+                        i = 0;
+                    } else {
+                        buffer[i - 1] = buffer[i];
+                        i++;
+                    }
+                }
+
+                lastReturnedIndex = -1;
+                tail = decrement(tail);
+                buffer[tail] = 0;
+                index = decrement(index);
+            }
+
+        };
+    }
+
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/field/AddressListField.java b/src/org/apache/james/mime4j/field/AddressListField.java
new file mode 100644
index 0000000..df9f398
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/AddressListField.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field;
+
+//BEGIN android-changed: Stubbing out logging
+import org.apache.james.mime4j.Log;
+import org.apache.james.mime4j.LogFactory;
+//END android-changed
+import org.apache.james.mime4j.field.address.AddressList;
+import org.apache.james.mime4j.field.address.parser.ParseException;
+
+public class AddressListField extends Field {
+    private AddressList addressList;
+    private ParseException parseException;
+
+    protected AddressListField(String name, String body, String raw, AddressList addressList, ParseException parseException) {
+        super(name, body, raw);
+        this.addressList = addressList;
+        this.parseException = parseException;
+    }
+
+    public AddressList getAddressList() {
+        return addressList;
+    }
+
+    public ParseException getParseException() {
+        return parseException;
+    }
+
+    public static class Parser implements FieldParser {
+        private static Log log = LogFactory.getLog(Parser.class);
+
+        public Field parse(final String name, final String body, final String raw) {
+            AddressList addressList = null;
+            ParseException parseException = null;
+            try {
+                addressList = AddressList.parse(body);
+            }
+            catch (ParseException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Parsing value '" + body + "': "+ e.getMessage());
+                }
+                parseException = e;
+            }
+            return new AddressListField(name, body, raw, addressList, parseException);
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/ContentTransferEncodingField.java b/src/org/apache/james/mime4j/field/ContentTransferEncodingField.java
new file mode 100644
index 0000000..73d8d23
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/ContentTransferEncodingField.java
@@ -0,0 +1,88 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field;
+
+
+
+/**
+ * Represents a <code>Content-Transfer-Encoding</code> field.
+ *
+ * 
+ * @version $Id: ContentTransferEncodingField.java,v 1.2 2004/10/02 12:41:11 ntherning Exp $
+ */
+public class ContentTransferEncodingField extends Field {
+    /**
+     * The <code>7bit</code> encoding.
+     */
+    public static final String ENC_7BIT = "7bit";
+    /**
+     * The <code>8bit</code> encoding.
+     */
+    public static final String ENC_8BIT = "8bit";
+    /**
+     * The <code>binary</code> encoding.
+     */
+    public static final String ENC_BINARY = "binary";
+    /**
+     * The <code>quoted-printable</code> encoding.
+     */
+    public static final String ENC_QUOTED_PRINTABLE = "quoted-printable";
+    /**
+     * The <code>base64</code> encoding.
+     */
+    public static final String ENC_BASE64 = "base64";
+    
+    private String encoding;
+    
+    protected ContentTransferEncodingField(String name, String body, String raw, String encoding) {
+        super(name, body, raw);
+        this.encoding = encoding;
+    }
+
+    /**
+     * Gets the encoding defined in this field.
+     * 
+     * @return the encoding or an empty string if not set.
+     */
+    public String getEncoding() {
+        return encoding;
+    }
+    
+    /**
+     * Gets the encoding of the given field if. Returns the default 
+     * <code>7bit</code> if not set or if
+     * <code>f</code> is <code>null</code>.
+     * 
+     * @return the encoding.
+     */
+    public static String getEncoding(ContentTransferEncodingField f) {
+        if (f != null && f.getEncoding().length() != 0) {
+            return f.getEncoding();
+        }
+        return ENC_7BIT;
+    }
+    
+    public static class Parser implements FieldParser {
+        public Field parse(final String name, final String body, final String raw) {
+            final String encoding = body.trim().toLowerCase();
+            return new ContentTransferEncodingField(name, body, raw, encoding);
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/ContentTypeField.java b/src/org/apache/james/mime4j/field/ContentTypeField.java
new file mode 100644
index 0000000..ad9f7f9
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/ContentTypeField.java
@@ -0,0 +1,259 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+//BEGIN android-changed: Stubbing out logging
+import org.apache.james.mime4j.Log;
+import org.apache.james.mime4j.LogFactory;
+//END android-changed
+import org.apache.james.mime4j.field.contenttype.parser.ContentTypeParser;
+import org.apache.james.mime4j.field.contenttype.parser.ParseException;
+import org.apache.james.mime4j.field.contenttype.parser.TokenMgrError;
+
+/**
+ * Represents a <code>Content-Type</code> field.
+ *
+ * <p>TODO: Remove dependency on Java 1.4 regexps</p>
+ *
+ *
+ * @version $Id: ContentTypeField.java,v 1.6 2005/01/27 14:16:31 ntherning Exp $
+ */
+public class ContentTypeField extends Field {
+
+    /**
+     * The prefix of all <code>multipart</code> MIME types.
+     */
+    public static final String TYPE_MULTIPART_PREFIX = "multipart/";
+    /**
+     * The <code>multipart/digest</code> MIME type.
+     */
+    public static final String TYPE_MULTIPART_DIGEST = "multipart/digest";
+    /**
+     * The <code>text/plain</code> MIME type.
+     */
+    public static final String TYPE_TEXT_PLAIN = "text/plain";
+    /**
+     * The <code>message/rfc822</code> MIME type.
+     */
+    public static final String TYPE_MESSAGE_RFC822 = "message/rfc822";
+    /**
+     * The name of the <code>boundary</code> parameter.
+     */
+    public static final String PARAM_BOUNDARY = "boundary";
+    /**
+     * The name of the <code>charset</code> parameter.
+     */
+    public static final String PARAM_CHARSET = "charset";
+
+    private String mimeType = "";
+    private Map<String, String> parameters = null;
+    private ParseException parseException;
+
+    protected ContentTypeField(String name, String body, String raw, String mimeType, Map<String, String> parameters, ParseException parseException) {
+        super(name, body, raw);
+        this.mimeType = mimeType;
+        this.parameters = parameters;
+        this.parseException = parseException;
+    }
+
+    /**
+     * Gets the exception that was raised during parsing of
+     * the field value, if any; otherwise, null.
+     */
+    public ParseException getParseException() {
+        return parseException;
+    }
+
+    /**
+     * Gets the MIME type defined in this Content-Type field.
+     *
+     * @return the MIME type or an empty string if not set.
+     */
+    public String getMimeType() {
+        return mimeType;
+    }
+
+    /**
+     * Gets the MIME type defined in the child's
+     * Content-Type field or derives a MIME type from the parent
+     * if child is <code>null</code> or hasn't got a MIME type value set.
+     * If child's MIME type is multipart but no boundary
+     * has been set the MIME type of child will be derived from
+     * the parent.
+     *
+     * @param child the child.
+     * @param parent the parent.
+     * @return the MIME type.
+     */
+    public static String getMimeType(ContentTypeField child,
+                                     ContentTypeField parent) {
+
+        if (child == null || child.getMimeType().length() == 0
+                || child.isMultipart() && child.getBoundary() == null) {
+
+            if (parent != null && parent.isMimeType(TYPE_MULTIPART_DIGEST)) {
+                return TYPE_MESSAGE_RFC822;
+            } else {
+                return TYPE_TEXT_PLAIN;
+            }
+        }
+
+        return child.getMimeType();
+    }
+
+    /**
+     * Gets the value of a parameter. Parameter names are case-insensitive.
+     *
+     * @param name the name of the parameter to get.
+     * @return the parameter value or <code>null</code> if not set.
+     */
+    public String getParameter(String name) {
+        return parameters != null
+                    ? parameters.get(name.toLowerCase())
+                    : null;
+    }
+
+    /**
+     * Gets all parameters.
+     *
+     * @return the parameters.
+     */
+    public Map<String, String> getParameters() {
+        if (parameters != null) {
+            return Collections.unmodifiableMap(parameters);
+        }
+        return Collections.emptyMap();
+    }
+
+    /**
+     * Gets the value of the <code>boundary</code> parameter if set.
+     *
+     * @return the <code>boundary</code> parameter value or <code>null</code>
+     *             if not set.
+     */
+    public String getBoundary() {
+        return getParameter(PARAM_BOUNDARY);
+    }
+
+    /**
+     * Gets the value of the <code>charset</code> parameter if set.
+     *
+     * @return the <code>charset</code> parameter value or <code>null</code>
+     *         if not set.
+     */
+    public String getCharset() {
+        return getParameter(PARAM_CHARSET);
+    }
+
+    /**
+     * Gets the value of the <code>charset</code> parameter if set for the
+     * given field. Returns the default <code>us-ascii</code> if not set or if
+     * <code>f</code> is <code>null</code>.
+     *
+     * @return the <code>charset</code> parameter value.
+     */
+    public static String getCharset(ContentTypeField f) {
+        if (f != null) {
+            if (f.getCharset() != null && f.getCharset().length() > 0) {
+                return f.getCharset();
+            }
+        }
+        return "us-ascii";
+    }
+
+    /**
+     * Determines if the MIME type of this field matches the given one.
+     *
+     * @param mimeType the MIME type to match against.
+     * @return <code>true</code> if the MIME type of this field matches,
+     *         <code>false</code> otherwise.
+     */
+    public boolean isMimeType(String mimeType) {
+        return this.mimeType.equalsIgnoreCase(mimeType);
+    }
+
+    /**
+     * Determines if the MIME type of this field is <code>multipart/*</code>.
+     *
+     * @return <code>true</code> if this field is has a <code>multipart/*</code>
+     *         MIME type, <code>false</code> otherwise.
+     */
+    public boolean isMultipart() {
+        return mimeType.startsWith(TYPE_MULTIPART_PREFIX);
+    }
+
+    public static class Parser implements FieldParser {
+        private static Log log = LogFactory.getLog(Parser.class);
+
+        public Field parse(final String name, final String body, final String raw) {
+            ParseException parseException = null;
+            String mimeType = "";
+            Map<String, String> parameters = null;
+
+            ContentTypeParser parser = new ContentTypeParser(new StringReader(body));
+            try {
+                parser.parseAll();
+            }
+            catch (ParseException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Parsing value '" + body + "': "+ e.getMessage());
+                }
+                parseException = e;
+            }
+            catch (TokenMgrError e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Parsing value '" + body + "': "+ e.getMessage());
+                }
+                parseException = new ParseException(e.getMessage());
+            }
+
+            try {
+                final String type = parser.getType();
+                final String subType = parser.getSubType();
+
+                if (type != null && subType != null) {
+                    mimeType = (type + "/" + parser.getSubType()).toLowerCase();
+
+                    ArrayList<String> paramNames = parser.getParamNames();
+                    ArrayList<String> paramValues = parser.getParamValues();
+
+                    if (paramNames != null && paramValues != null) {
+                        for (int i = 0; i < paramNames.size() && i < paramValues.size(); i++) {
+                            if (parameters == null)
+                                parameters = new HashMap<String, String>((int)(paramNames.size() * 1.3 + 1));
+                            String paramName = paramNames.get(i).toLowerCase();
+                            String paramValue = paramValues.get(i);
+                            parameters.put(paramName, paramValue);
+                        }
+                    }
+                }
+            }
+            catch (NullPointerException npe) {
+            }
+            return new ContentTypeField(name, body, raw, mimeType, parameters, parseException);
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/DateTimeField.java b/src/org/apache/james/mime4j/field/DateTimeField.java
new file mode 100644
index 0000000..5215534
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/DateTimeField.java
@@ -0,0 +1,73 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field;
+
+//BEGIN android-changed: Stubbing out logging
+
+import com.android.phone.common.mail.utils.LogUtils;
+
+import org.apache.james.mime4j.Log;
+import org.apache.james.mime4j.LogFactory;
+//END
+import org.apache.james.mime4j.field.datetime.DateTime;
+import org.apache.james.mime4j.field.datetime.parser.ParseException;
+
+import java.util.Date;
+
+public class DateTimeField extends Field {
+    private Date date;
+    private ParseException parseException;
+
+    protected DateTimeField(String name, String body, String raw, Date date, ParseException parseException) {
+        super(name, body, raw);
+        this.date = date;
+        this.parseException = parseException;
+    }
+
+    public Date getDate() {
+        return date;
+    }
+
+    public ParseException getParseException() {
+        return parseException;
+    }
+
+    public static class Parser implements FieldParser {
+        private static Log log = LogFactory.getLog(Parser.class);
+
+        public Field parse(final String name, String body, final String raw) {
+            Date date = null;
+            ParseException parseException = null;
+            //BEGIN android-changed
+            body = LogUtils.cleanUpMimeDate(body);
+            //END android-changed
+            try {
+                date = DateTime.parse(body).getDate();
+            }
+            catch (ParseException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Parsing value '" + body + "': "+ e.getMessage());
+                }
+                parseException = e;
+            }
+            return new DateTimeField(name, body, raw, date, parseException);
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/DefaultFieldParser.java b/src/org/apache/james/mime4j/field/DefaultFieldParser.java
new file mode 100644
index 0000000..3695afe
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/DefaultFieldParser.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2006 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field;
+
+public class DefaultFieldParser extends DelegatingFieldParser {
+    
+    public DefaultFieldParser() {
+        setFieldParser(Field.CONTENT_TRANSFER_ENCODING, new ContentTransferEncodingField.Parser());
+        setFieldParser(Field.CONTENT_TYPE, new ContentTypeField.Parser());
+        
+        final DateTimeField.Parser dateTimeParser = new DateTimeField.Parser();
+        setFieldParser(Field.DATE, dateTimeParser);
+        setFieldParser(Field.RESENT_DATE, dateTimeParser);
+        
+        final MailboxListField.Parser mailboxListParser = new MailboxListField.Parser();
+        setFieldParser(Field.FROM, mailboxListParser);
+        setFieldParser(Field.RESENT_FROM, mailboxListParser);
+        
+        final MailboxField.Parser mailboxParser = new MailboxField.Parser();
+        setFieldParser(Field.SENDER, mailboxParser);
+        setFieldParser(Field.RESENT_SENDER, mailboxParser);
+        
+        final AddressListField.Parser addressListParser = new AddressListField.Parser();
+        setFieldParser(Field.TO, addressListParser);
+        setFieldParser(Field.RESENT_TO, addressListParser);
+        setFieldParser(Field.CC, addressListParser);
+        setFieldParser(Field.RESENT_CC, addressListParser);
+        setFieldParser(Field.BCC, addressListParser);
+        setFieldParser(Field.RESENT_BCC, addressListParser);
+        setFieldParser(Field.REPLY_TO, addressListParser);
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/DelegatingFieldParser.java b/src/org/apache/james/mime4j/field/DelegatingFieldParser.java
new file mode 100644
index 0000000..32b69ec
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/DelegatingFieldParser.java
@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2006 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DelegatingFieldParser implements FieldParser {
+
+    private Map<String, FieldParser> parsers = new HashMap<String, FieldParser>();
+    private FieldParser defaultParser = new UnstructuredField.Parser();
+
+    /**
+     * Sets the parser used for the field named <code>name</code>.
+     * @param name the name of the field
+     * @param parser the parser for fields named <code>name</code>
+     */
+    public void setFieldParser(final String name, final FieldParser parser) {
+        parsers.put(name.toLowerCase(), parser);
+    }
+
+    public FieldParser getParser(final String name) {
+        final FieldParser field = parsers.get(name.toLowerCase());
+        if(field==null) {
+            return defaultParser;
+        }
+        return field;
+    }
+
+    public Field parse(final String name, final String body, final String raw) {
+        final FieldParser parser = getParser(name);
+        return parser.parse(name, body, raw);
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/Field.java b/src/org/apache/james/mime4j/field/Field.java
new file mode 100644
index 0000000..4dea5c5
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/Field.java
@@ -0,0 +1,192 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The base class of all field classes.
+ *
+ * 
+ * @version $Id: Field.java,v 1.6 2004/10/25 07:26:46 ntherning Exp $
+ */
+public abstract class Field {
+    public static final String SENDER = "Sender";
+    public static final String FROM = "From";
+    public static final String TO = "To";
+    public static final String CC = "Cc";
+    public static final String BCC = "Bcc";
+    public static final String REPLY_TO = "Reply-To";
+    public static final String RESENT_SENDER = "Resent-Sender";
+    public static final String RESENT_FROM = "Resent-From";
+    public static final String RESENT_TO = "Resent-To";
+    public static final String RESENT_CC = "Resent-Cc";
+    public static final String RESENT_BCC = "Resent-Bcc";
+
+    public static final String DATE = "Date";
+    public static final String RESENT_DATE = "Resent-Date";
+
+    public static final String SUBJECT = "Subject";
+    public static final String CONTENT_TYPE = "Content-Type";
+    public static final String CONTENT_TRANSFER_ENCODING = 
+                                        "Content-Transfer-Encoding";
+    
+    private static final String FIELD_NAME_PATTERN = 
+        "^([\\x21-\\x39\\x3b-\\x7e]+)[ \t]*:";
+    private static final Pattern fieldNamePattern = 
+        Pattern.compile(FIELD_NAME_PATTERN);
+        
+    private static final DefaultFieldParser parser = new DefaultFieldParser();
+    
+    private final String name;
+    private final String body;
+    private final String raw;
+    
+    protected Field(final String name, final String body, final String raw) {
+        this.name = name;
+        this.body = body;
+        this.raw = raw;
+    }
+    
+    /**
+     * Parses the given string and returns an instance of the 
+     * <code>Field</code> class. The type of the class returned depends on
+     * the field name:
+     * <table>
+     *      <tr>
+     *          <td><em>Field name</em></td><td><em>Class returned</em></td>
+     *          <td>Content-Type</td><td>org.apache.james.mime4j.field.ContentTypeField</td>
+     *          <td>other</td><td>org.apache.james.mime4j.field.UnstructuredField</td>
+     *      </tr>
+     * </table>
+     * 
+     * @param s the string to parse.
+     * @return a <code>Field</code> instance.
+     * @throws IllegalArgumentException on parse errors.
+     */
+    public static Field parse(final String raw) {
+        
+        /*
+         * Unfold the field.
+         */
+        final String unfolded = raw.replaceAll("\r|\n", "");
+        
+        /*
+         * Split into name and value.
+         */
+        final Matcher fieldMatcher = fieldNamePattern.matcher(unfolded);
+        if (!fieldMatcher.find()) {
+            throw new IllegalArgumentException("Invalid field in string");
+        }
+        final String name = fieldMatcher.group(1);
+        
+        String body = unfolded.substring(fieldMatcher.end());
+        if (body.length() > 0 && body.charAt(0) == ' ') {
+            body = body.substring(1);
+        }
+        
+        return parser.parse(name, body, raw);
+    }
+    
+    /**
+     * Gets the default parser used to parse fields.
+     * @return the default field parser
+     */
+    public static DefaultFieldParser getParser() {
+        return parser;
+    }
+    
+    /**
+     * Gets the name of the field (<code>Subject</code>, 
+     * <code>From</code>, etc).
+     * 
+     * @return the field name.
+     */
+    public String getName() {
+        return name;
+    }
+    
+    /**
+     * Gets the original raw field string.
+     * 
+     * @return the original raw field string.
+     */
+    public String getRaw() {
+        return raw;
+    }
+    
+    /**
+     * Gets the unfolded, unparsed and possibly encoded (see RFC 2047) field 
+     * body string.
+     * 
+     * @return the unfolded unparsed field body string.
+     */
+    public String getBody() {
+        return body;
+    }
+    
+    /**
+     * Determines if this is a <code>Content-Type</code> field.
+     * 
+     * @return <code>true</code> if this is a <code>Content-Type</code> field,
+     *         <code>false</code> otherwise.
+     */
+    public boolean isContentType() {
+        return CONTENT_TYPE.equalsIgnoreCase(name);
+    }
+    
+    /**
+     * Determines if this is a <code>Subject</code> field.
+     * 
+     * @return <code>true</code> if this is a <code>Subject</code> field,
+     *         <code>false</code> otherwise.
+     */
+    public boolean isSubject() {
+        return SUBJECT.equalsIgnoreCase(name);
+    }
+    
+    /**
+     * Determines if this is a <code>From</code> field.
+     * 
+     * @return <code>true</code> if this is a <code>From</code> field,
+     *         <code>false</code> otherwise.
+     */
+    public boolean isFrom() {
+        return FROM.equalsIgnoreCase(name);
+    }
+    
+    /**
+     * Determines if this is a <code>To</code> field.
+     * 
+     * @return <code>true</code> if this is a <code>To</code> field,
+     *         <code>false</code> otherwise.
+     */
+    public boolean isTo() {
+        return TO.equalsIgnoreCase(name);
+    }
+    
+    /**
+     * @see #getRaw()
+     */
+    public String toString() {
+        return raw;
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/FieldParser.java b/src/org/apache/james/mime4j/field/FieldParser.java
new file mode 100644
index 0000000..78aaf13
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/FieldParser.java
@@ -0,0 +1,21 @@
+/*
+ *  Copyright 2006 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field;
+
+public interface FieldParser {
+    
+    Field parse(final String name, final String body, final String raw);
+}
diff --git a/src/org/apache/james/mime4j/field/MailboxField.java b/src/org/apache/james/mime4j/field/MailboxField.java
new file mode 100644
index 0000000..f159800
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/MailboxField.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field;
+
+//BEGIN android-changed: Stubbing out logging
+import org.apache.james.mime4j.Log;
+import org.apache.james.mime4j.LogFactory;
+//END android-changed
+import org.apache.james.mime4j.field.address.AddressList;
+import org.apache.james.mime4j.field.address.Mailbox;
+import org.apache.james.mime4j.field.address.MailboxList;
+import org.apache.james.mime4j.field.address.parser.ParseException;
+
+public class MailboxField extends Field {
+    private final Mailbox mailbox;
+    private final ParseException parseException;
+
+    protected MailboxField(final String name, final String body, final String raw, final Mailbox mailbox, final ParseException parseException) {
+        super(name, body, raw);
+        this.mailbox = mailbox;
+        this.parseException = parseException;
+    }
+
+    public Mailbox getMailbox() {
+        return mailbox;
+    }
+
+    public ParseException getParseException() {
+        return parseException;
+    }
+    
+    public static class Parser implements FieldParser {
+        private static Log log = LogFactory.getLog(Parser.class);
+
+        public Field parse(final String name, final String body, final String raw) {
+            Mailbox mailbox = null;
+            ParseException parseException = null;
+            try {
+                MailboxList mailboxList = AddressList.parse(body).flatten();
+                if (mailboxList.size() > 0) {
+                    mailbox = mailboxList.get(0);
+                }
+            }
+            catch (ParseException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Parsing value '" + body + "': "+ e.getMessage());
+                }
+                parseException = e;
+            }
+            return new MailboxField(name, body, raw, mailbox, parseException);
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/MailboxListField.java b/src/org/apache/james/mime4j/field/MailboxListField.java
new file mode 100644
index 0000000..23378d4
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/MailboxListField.java
@@ -0,0 +1,67 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field;
+
+//BEGIN android-changed: Stubbing out logging
+import org.apache.james.mime4j.Log;
+import org.apache.james.mime4j.LogFactory;
+//END android-changed
+import org.apache.james.mime4j.field.address.AddressList;
+import org.apache.james.mime4j.field.address.MailboxList;
+import org.apache.james.mime4j.field.address.parser.ParseException;
+
+public class MailboxListField extends Field {
+    
+    private MailboxList mailboxList;
+    private ParseException parseException;
+
+    protected MailboxListField(final String name, final String body, final String raw, final MailboxList mailboxList, final ParseException parseException) {
+        super(name, body, raw);
+        this.mailboxList = mailboxList;
+        this.parseException = parseException;
+    }
+
+    public MailboxList getMailboxList() {
+        return mailboxList;
+    }
+
+    public ParseException getParseException() {
+        return parseException;
+    }
+    
+    public static class Parser implements FieldParser {
+        private static Log log = LogFactory.getLog(Parser.class);
+
+        public Field parse(final String name, final String body, final String raw) {
+            MailboxList mailboxList = null;
+            ParseException parseException = null;
+            try {
+                mailboxList = AddressList.parse(body).flatten();
+            }
+            catch (ParseException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Parsing value '" + body + "': "+ e.getMessage());
+                }
+                parseException = e;
+            }
+            return new MailboxListField(name, body, raw, mailboxList, parseException);
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/UnstructuredField.java b/src/org/apache/james/mime4j/field/UnstructuredField.java
new file mode 100644
index 0000000..6084e44
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/UnstructuredField.java
@@ -0,0 +1,49 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field;
+
+import org.apache.james.mime4j.decoder.DecoderUtil;
+
+
+/**
+ * Simple unstructured field such as <code>Subject</code>.
+ *
+ * 
+ * @version $Id: UnstructuredField.java,v 1.3 2004/10/25 07:26:46 ntherning Exp $
+ */
+public class UnstructuredField extends Field {
+    private String value;
+    
+    protected UnstructuredField(String name, String body, String raw, String value) {
+        super(name, body, raw);
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public static class Parser implements FieldParser {
+        public Field parse(final String name, final String body, final String raw) {
+            final String value = DecoderUtil.decodeEncodedWords(body);
+            return new UnstructuredField(name, body, raw, value);
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/address/Address.java b/src/org/apache/james/mime4j/field/address/Address.java
new file mode 100644
index 0000000..3e24e91
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/Address.java
@@ -0,0 +1,52 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address;
+
+import java.util.ArrayList;
+
+/**
+ * The abstract base for classes that represent RFC2822 addresses.
+ * This includes groups and mailboxes.
+ *
+ * Currently, no public methods are introduced on this class.
+ *
+ *
+ */
+public abstract class Address {
+
+	/**
+	 * Adds any mailboxes represented by this address
+	 * into the given ArrayList. Note that this method
+	 * has default (package) access, so a doAddMailboxesTo
+	 * method is needed to allow the behavior to be
+	 * overridden by subclasses.
+	 */
+	final void addMailboxesTo(ArrayList<Address> results) {
+		doAddMailboxesTo(results);
+	}
+
+	/**
+	 * Adds any mailboxes represented by this address
+	 * into the given ArrayList. Must be overridden by
+	 * concrete subclasses.
+	 */
+	protected abstract void doAddMailboxesTo(ArrayList<Address> results);
+
+}
diff --git a/src/org/apache/james/mime4j/field/address/AddressList.java b/src/org/apache/james/mime4j/field/address/AddressList.java
new file mode 100644
index 0000000..1829e79
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/AddressList.java
@@ -0,0 +1,138 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address;
+
+import org.apache.james.mime4j.field.address.parser.AddressListParser;
+import org.apache.james.mime4j.field.address.parser.ParseException;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+
+/**
+ * An immutable, random-access list of Address objects.
+ *
+ *
+ */
+public class AddressList {
+
+	private ArrayList<Address> addresses;
+
+	/**
+	 * @param addresses An ArrayList that contains only Address objects.
+	 * @param dontCopy true iff it is not possible for the addresses ArrayList to be modified by someone else.
+	 */
+	public AddressList(ArrayList<Address> addresses, boolean dontCopy) {
+		if (addresses != null)
+			this.addresses = (dontCopy ? addresses : new ArrayList<Address>(addresses));
+		else
+			this.addresses = new ArrayList<Address>(0);
+	}
+
+	/**
+	 * The number of elements in this list.
+	 */
+	public int size() {
+		return addresses.size();
+	}
+
+	/**
+	 * Gets an address.
+	 */
+	public Address get(int index) {
+		if (0 > index || size() <= index)
+			throw new IndexOutOfBoundsException();
+		return addresses.get(index);
+	}
+
+	/**
+	 * Returns a flat list of all mailboxes represented
+	 * in this address list. Use this if you don't care
+	 * about grouping.
+	 */
+	public MailboxList flatten() {
+		// in the common case, all addresses are mailboxes
+		boolean groupDetected = false;
+		for (int i = 0; i < size(); i++) {
+			if (!(get(i) instanceof Mailbox)) {
+				groupDetected = true;
+				break;
+			}
+		}
+
+		if (!groupDetected)
+			return new MailboxList(addresses, true);
+
+		ArrayList<Address> results = new ArrayList<Address>();
+		for (int i = 0; i < size(); i++) {
+			Address addr = get(i);
+			addr.addMailboxesTo(results);
+		}
+
+		// copy-on-construct this time, because subclasses
+		// could have held onto a reference to the results
+		return new MailboxList(results, false);
+	}
+
+	/**
+	 * Dumps a representation of this address list to
+	 * stdout, for debugging purposes.
+	 */
+	public void print() {
+		for (int i = 0; i < size(); i++) {
+			Address addr = get(i);
+			System.out.println(addr.toString());
+		}
+	}
+
+	/**
+	 * Parse the address list string, such as the value
+	 * of a From, To, Cc, Bcc, Sender, or Reply-To
+	 * header.
+	 *
+	 * The string MUST be unfolded already.
+	 */
+	public static AddressList parse(String rawAddressList) throws ParseException {
+		AddressListParser parser = new AddressListParser(new StringReader(rawAddressList));
+		return Builder.getInstance().buildAddressList(parser.parse());
+	}
+
+	/**
+	 * Test console.
+	 */
+	public static void main(String[] args) throws Exception {
+		java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
+		while (true) {
+			try {
+				System.out.print("> ");
+				String line = reader.readLine();
+				if (line.length() == 0 || line.toLowerCase().equals("exit") || line.toLowerCase().equals("quit")) {
+					System.out.println("Goodbye.");
+					return;
+				}
+				AddressList list = parse(line);
+				list.print();
+			}
+			catch(Exception e) {
+				e.printStackTrace();
+				Thread.sleep(300);
+			}
+		}
+	}
+}
diff --git a/src/org/apache/james/mime4j/field/address/Builder.java b/src/org/apache/james/mime4j/field/address/Builder.java
new file mode 100644
index 0000000..3bcd15b
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/Builder.java
@@ -0,0 +1,243 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.james.mime4j.decoder.DecoderUtil;
+import org.apache.james.mime4j.field.address.parser.ASTaddr_spec;
+import org.apache.james.mime4j.field.address.parser.ASTaddress;
+import org.apache.james.mime4j.field.address.parser.ASTaddress_list;
+import org.apache.james.mime4j.field.address.parser.ASTangle_addr;
+import org.apache.james.mime4j.field.address.parser.ASTdomain;
+import org.apache.james.mime4j.field.address.parser.ASTgroup_body;
+import org.apache.james.mime4j.field.address.parser.ASTlocal_part;
+import org.apache.james.mime4j.field.address.parser.ASTmailbox;
+import org.apache.james.mime4j.field.address.parser.ASTname_addr;
+import org.apache.james.mime4j.field.address.parser.ASTphrase;
+import org.apache.james.mime4j.field.address.parser.ASTroute;
+import org.apache.james.mime4j.field.address.parser.Node;
+import org.apache.james.mime4j.field.address.parser.SimpleNode;
+import org.apache.james.mime4j.field.address.parser.Token;
+
+/**
+ * Transforms the JJTree-generated abstract syntax tree
+ * into a graph of org.apache.james.mime4j.field.address objects.
+ *
+ *
+ */
+class Builder {
+
+	private static Builder singleton = new Builder();
+
+	public static Builder getInstance() {
+		return singleton;
+	}
+
+
+
+	public AddressList buildAddressList(ASTaddress_list node) {
+		ArrayList<Address> list = new ArrayList<Address>();
+		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
+			ASTaddress childNode = (ASTaddress) node.jjtGetChild(i);
+			Address address = buildAddress(childNode);
+			list.add(address);
+		}
+		return new AddressList(list, true);
+	}
+
+	private Address buildAddress(ASTaddress node) {
+		ChildNodeIterator it = new ChildNodeIterator(node);
+		Node n = it.nextNode();
+		if (n instanceof ASTaddr_spec) {
+			return buildAddrSpec((ASTaddr_spec)n);
+		}
+		else if (n instanceof ASTangle_addr) {
+			return buildAngleAddr((ASTangle_addr)n);
+		}
+		else if (n instanceof ASTphrase) {
+			String name = buildString((ASTphrase)n, false);
+			Node n2 = it.nextNode();
+			if (n2 instanceof ASTgroup_body) {
+				return new Group(name, buildGroupBody((ASTgroup_body)n2));
+			}
+			else if (n2 instanceof ASTangle_addr) {
+                name = DecoderUtil.decodeEncodedWords(name);
+				return new NamedMailbox(name, buildAngleAddr((ASTangle_addr)n2));
+			}
+			else {
+				throw new IllegalStateException();
+			}
+		}
+		else {
+			throw new IllegalStateException();
+		}
+	}
+
+
+
+	private MailboxList buildGroupBody(ASTgroup_body node) {
+		ArrayList<Address> results = new ArrayList<Address>();
+		ChildNodeIterator it = new ChildNodeIterator(node);
+		while (it.hasNext()) {
+			Node n = it.nextNode();
+			if (n instanceof ASTmailbox)
+				results.add(buildMailbox((ASTmailbox)n));
+			else
+				throw new IllegalStateException();
+		}
+		return new MailboxList(results, true);
+	}
+
+	private Mailbox buildMailbox(ASTmailbox node) {
+		ChildNodeIterator it = new ChildNodeIterator(node);
+		Node n = it.nextNode();
+		if (n instanceof ASTaddr_spec) {
+			return buildAddrSpec((ASTaddr_spec)n);
+		}
+		else if (n instanceof ASTangle_addr) {
+			return buildAngleAddr((ASTangle_addr)n);
+		}
+		else if (n instanceof ASTname_addr) {
+			return buildNameAddr((ASTname_addr)n);
+		}
+		else {
+			throw new IllegalStateException();
+		}
+	}
+
+	private NamedMailbox buildNameAddr(ASTname_addr node) {
+		ChildNodeIterator it = new ChildNodeIterator(node);
+		Node n = it.nextNode();
+		String name;
+		if (n instanceof ASTphrase) {
+			name = buildString((ASTphrase)n, false);
+		}
+		else {
+			throw new IllegalStateException();
+		}
+
+		n = it.nextNode();
+		if (n instanceof ASTangle_addr) {
+            name = DecoderUtil.decodeEncodedWords(name);
+			return new NamedMailbox(name, buildAngleAddr((ASTangle_addr) n));
+		}
+		else {
+			throw new IllegalStateException();
+		}
+	}
+
+	private Mailbox buildAngleAddr(ASTangle_addr node) {
+		ChildNodeIterator it = new ChildNodeIterator(node);
+		DomainList route = null;
+		Node n = it.nextNode();
+		if (n instanceof ASTroute) {
+			route = buildRoute((ASTroute)n);
+			n = it.nextNode();
+		}
+		else if (n instanceof ASTaddr_spec)
+			; // do nothing
+		else
+			throw new IllegalStateException();
+
+		if (n instanceof ASTaddr_spec)
+			return buildAddrSpec(route, (ASTaddr_spec)n);
+		else
+			throw new IllegalStateException();
+	}
+
+	private DomainList buildRoute(ASTroute node) {
+		ArrayList<String> results = new ArrayList<String>(node.jjtGetNumChildren());
+		ChildNodeIterator it = new ChildNodeIterator(node);
+		while (it.hasNext()) {
+			Node n = it.nextNode();
+			if (n instanceof ASTdomain)
+				results.add(buildString((ASTdomain)n, true));
+			else
+				throw new IllegalStateException();
+		}
+		return new DomainList(results, true);
+	}
+
+	private Mailbox buildAddrSpec(ASTaddr_spec node) {
+		return buildAddrSpec(null, node);
+	}
+	private Mailbox buildAddrSpec(DomainList route, ASTaddr_spec node) {
+		ChildNodeIterator it = new ChildNodeIterator(node);
+		String localPart = buildString((ASTlocal_part)it.nextNode(), true);
+		String domain = buildString((ASTdomain)it.nextNode(), true);
+		return new Mailbox(route, localPart, domain);
+	}
+
+
+	private String buildString(SimpleNode node, boolean stripSpaces) {
+		Token head = node.firstToken;
+		Token tail = node.lastToken;
+		StringBuffer out = new StringBuffer();
+
+		while (head != tail) {
+			out.append(head.image);
+			head = head.next;
+			if (!stripSpaces)
+				addSpecials(out, head.specialToken);
+		}
+		out.append(tail.image);
+
+		return out.toString();
+	}
+
+	private void addSpecials(StringBuffer out, Token specialToken) {
+		if (specialToken != null) {
+			addSpecials(out, specialToken.specialToken);
+			out.append(specialToken.image);
+		}
+	}
+
+	private static class ChildNodeIterator implements Iterator<Node> {
+
+		private SimpleNode simpleNode;
+		private int index;
+		private int len;
+
+		public ChildNodeIterator(SimpleNode simpleNode) {
+			this.simpleNode = simpleNode;
+			this.len = simpleNode.jjtGetNumChildren();
+			this.index = 0;
+		}
+
+		public void remove() {
+			throw new UnsupportedOperationException();
+		}
+
+		public boolean hasNext() {
+			return index < len;
+		}
+
+		public Node next() {
+			return nextNode();
+		}
+
+		public Node nextNode() {
+			return simpleNode.jjtGetChild(index++);
+		}
+
+	}
+}
diff --git a/src/org/apache/james/mime4j/field/address/DomainList.java b/src/org/apache/james/mime4j/field/address/DomainList.java
new file mode 100644
index 0000000..49b0f3b
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/DomainList.java
@@ -0,0 +1,76 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address;
+
+import java.util.ArrayList;
+
+/**
+ * An immutable, random-access list of Strings (that
+ * are supposedly domain names or domain literals).
+ *
+ *
+ */
+public class DomainList {
+	private ArrayList<String> domains;
+
+	/**
+	 * @param domains An ArrayList that contains only String objects.
+	 * @param dontCopy true iff it is not possible for the domains ArrayList to be modified by someone else.
+	 */
+	public DomainList(ArrayList<String> domains, boolean dontCopy) {
+		if (domains != null)
+			this.domains = (dontCopy ? domains : new ArrayList<String>(domains));
+		else
+			this.domains = new ArrayList<String>(0);
+	}
+
+	/**
+	 * The number of elements in this list.
+	 */
+	public int size() {
+		return domains.size();
+	}
+
+	/**
+	 * Gets the domain name or domain literal at the
+	 * specified index.
+	 * @throws IndexOutOfBoundsException If index is &lt; 0 or &gt;= size().
+	 */
+	public String get(int index) {
+		if (0 > index || size() <= index)
+			throw new IndexOutOfBoundsException();
+		return domains.get(index);
+	}
+
+	/**
+	 * Returns the list of domains formatted as a route
+	 * string (not including the trailing ':').
+	 */
+	public String toRouteString() {
+		StringBuffer out = new StringBuffer();
+		for (int i = 0; i < domains.size(); i++) {
+			out.append("@");
+			out.append(get(i));
+			if (i + 1 < domains.size())
+				out.append(",");
+		}
+		return out.toString();
+	}
+}
diff --git a/src/org/apache/james/mime4j/field/address/Group.java b/src/org/apache/james/mime4j/field/address/Group.java
new file mode 100644
index 0000000..c0ab7f7
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/Group.java
@@ -0,0 +1,75 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address;
+
+import java.util.ArrayList;
+
+/**
+ * A named group of zero or more mailboxes.
+ *
+ *
+ */
+public class Group extends Address {
+	private String name;
+	private MailboxList mailboxList;
+
+	/**
+	 * @param name The group name.
+	 * @param mailboxes The mailboxes in this group.
+	 */
+	public Group(String name, MailboxList mailboxes) {
+		this.name = name;
+		this.mailboxList = mailboxes;
+	}
+
+	/**
+	 * Returns the group name.
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * Returns the mailboxes in this group.
+	 */
+	public MailboxList getMailboxes() {
+		return mailboxList;
+	}
+
+	@Override
+	public String toString() {
+		StringBuffer buf = new StringBuffer();
+		buf.append(name);
+		buf.append(":");
+		for (int i = 0; i < mailboxList.size(); i++) {
+			buf.append(mailboxList.get(i).toString());
+			if (i + 1 < mailboxList.size())
+				buf.append(",");
+		}
+		buf.append(";");
+		return buf.toString();
+	}
+
+	@Override
+	protected void doAddMailboxesTo(ArrayList<Address> results) {
+		for (int i = 0; i < mailboxList.size(); i++)
+			results.add(mailboxList.get(i));
+	}
+}
diff --git a/src/org/apache/james/mime4j/field/address/Mailbox.java b/src/org/apache/james/mime4j/field/address/Mailbox.java
new file mode 100644
index 0000000..25f2548
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/Mailbox.java
@@ -0,0 +1,121 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a single e-mail address.
+ *
+ *
+ */
+public class Mailbox extends Address {
+	private DomainList route;
+	private String localPart;
+	private String domain;
+
+	/**
+	 * Creates a mailbox without a route. Routes are obsolete.
+	 * @param localPart The part of the e-mail address to the left of the "@".
+	 * @param domain The part of the e-mail address to the right of the "@".
+	 */
+	public Mailbox(String localPart, String domain) {
+		this(null, localPart, domain);
+	}
+
+	/**
+	 * Creates a mailbox with a route. Routes are obsolete.
+	 * @param route The zero or more domains that make up the route. Can be null.
+	 * @param localPart The part of the e-mail address to the left of the "@".
+	 * @param domain The part of the e-mail address to the right of the "@".
+	 */
+	public Mailbox(DomainList route, String localPart, String domain) {
+		this.route = route;
+		this.localPart = localPart;
+		this.domain = domain;
+	}
+
+	/**
+	 * Returns the route list.
+	 */
+	public DomainList getRoute() {
+		return route;
+	}
+
+	/**
+	 * Returns the left part of the e-mail address
+	 * (before "@").
+	 */
+	public String getLocalPart() {
+		return localPart;
+	}
+
+	/**
+	 * Returns the right part of the e-mail address
+	 * (after "@").
+	 */
+	public String getDomain() {
+		return domain;
+	}
+
+	/**
+	 * Formats the address as a string, not including
+	 * the route.
+	 *
+	 * @see #getAddressString(boolean)
+	 */
+	public String getAddressString() {
+		return getAddressString(false);
+	}
+
+	/**
+	 * Note that this value may not be usable
+	 * for transport purposes, only display purposes.
+	 *
+	 * For example, if the unparsed address was
+	 *
+	 *   <"Joe Cheng"@joecheng.com>
+	 *
+	 * this method would return
+	 *
+	 *   <Joe [email protected]>
+	 *
+	 * which is not valid for transport; the local part
+	 * would need to be re-quoted.
+	 *
+	 * @param includeRoute true if the route should be included if it exists.
+	 */
+	public String getAddressString(boolean includeRoute) {
+		return "<" + (!includeRoute || route == null ? "" : route.toRouteString() + ":")
+			+ localPart
+			+ (domain == null ? "" : "@")
+			+ domain + ">";
+	}
+
+	@Override
+	protected final void doAddMailboxesTo(ArrayList<Address> results) {
+		results.add(this);
+	}
+
+	@Override
+	public String toString() {
+		return getAddressString();
+	}
+}
diff --git a/src/org/apache/james/mime4j/field/address/MailboxList.java b/src/org/apache/james/mime4j/field/address/MailboxList.java
new file mode 100644
index 0000000..2c9efb3
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/MailboxList.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address;
+
+import java.util.ArrayList;
+
+/**
+ * An immutable, random-access list of Mailbox objects.
+ *
+ *
+ */
+public class MailboxList {
+
+	private ArrayList<Address> mailboxes;
+
+	/**
+	 * @param mailboxes An ArrayList that contains only Mailbox objects.
+	 * @param dontCopy true iff it is not possible for the mailboxes ArrayList to be modified by someone else.
+	 */
+	public MailboxList(ArrayList<Address> mailboxes, boolean dontCopy) {
+		if (mailboxes != null)
+			this.mailboxes = (dontCopy ? mailboxes : new ArrayList<Address>(mailboxes));
+		else
+			this.mailboxes = new ArrayList<Address>(0);
+	}
+
+	/**
+	 * The number of elements in this list.
+	 */
+	public int size() {
+		return mailboxes.size();
+	}
+
+	/**
+	 * Gets an address.
+	 */
+	public Mailbox get(int index) {
+		if (0 > index || size() <= index)
+			throw new IndexOutOfBoundsException();
+		return (Mailbox)mailboxes.get(index);
+	}
+
+	/**
+	 * Dumps a representation of this mailbox list to
+	 * stdout, for debugging purposes.
+	 */
+	public void print() {
+		for (int i = 0; i < size(); i++) {
+			Mailbox mailbox = get(i);
+			System.out.println(mailbox.toString());
+		}
+	}
+
+}
diff --git a/src/org/apache/james/mime4j/field/address/NamedMailbox.java b/src/org/apache/james/mime4j/field/address/NamedMailbox.java
new file mode 100644
index 0000000..4b83060
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/NamedMailbox.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address;
+
+/**
+ * A Mailbox that has a name/description.
+ *
+ *
+ */
+public class NamedMailbox extends Mailbox {
+	private String name;
+
+	/**
+	 * @see Mailbox#Mailbox(String, String)
+	 */
+	public NamedMailbox(String name, String localPart, String domain) {
+		super(localPart, domain);
+		this.name = name;
+	}
+
+	/**
+	 * @see Mailbox#Mailbox(DomainList, String, String)
+	 */
+	public NamedMailbox(String name, DomainList route, String localPart, String domain) {
+		super(route, localPart, domain);
+		this.name = name;
+	}
+
+	/**
+	 * Creates a named mailbox based on an unnamed mailbox.
+	 */
+	public NamedMailbox(String name, Mailbox baseMailbox) {
+		super(baseMailbox.getRoute(), baseMailbox.getLocalPart(), baseMailbox.getDomain());
+		this.name = name;
+	}
+
+	/**
+	 * Returns the name of the mailbox.
+	 */
+	public String getName() {
+		return this.name;
+	}
+
+	/**
+	 * Same features (or problems) as Mailbox.getAddressString(boolean),
+	 * only more so.
+	 *
+	 * @see Mailbox#getAddressString(boolean)
+	 */
+	@Override
+	public String getAddressString(boolean includeRoute) {
+		return (name == null ? "" : name + " ") + super.getAddressString(includeRoute);
+	}
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java b/src/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java
new file mode 100644
index 0000000..4d56d00
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTaddr_spec.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTaddr_spec.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTaddr_spec extends SimpleNode {
+  public ASTaddr_spec(int id) {
+    super(id);
+  }
+
+  public ASTaddr_spec(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTaddress.java b/src/org/apache/james/mime4j/field/address/parser/ASTaddress.java
new file mode 100644
index 0000000..47bdeda
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTaddress.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTaddress.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTaddress extends SimpleNode {
+  public ASTaddress(int id) {
+    super(id);
+  }
+
+  public ASTaddress(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java b/src/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java
new file mode 100644
index 0000000..737840e
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTaddress_list.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTaddress_list.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTaddress_list extends SimpleNode {
+  public ASTaddress_list(int id) {
+    super(id);
+  }
+
+  public ASTaddress_list(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java b/src/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java
new file mode 100644
index 0000000..8cb8f42
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTangle_addr.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTangle_addr.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTangle_addr extends SimpleNode {
+  public ASTangle_addr(int id) {
+    super(id);
+  }
+
+  public ASTangle_addr(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTdomain.java b/src/org/apache/james/mime4j/field/address/parser/ASTdomain.java
new file mode 100644
index 0000000..b526643
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTdomain.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTdomain.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTdomain extends SimpleNode {
+  public ASTdomain(int id) {
+    super(id);
+  }
+
+  public ASTdomain(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java b/src/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java
new file mode 100644
index 0000000..f6017b9
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTgroup_body.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTgroup_body.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTgroup_body extends SimpleNode {
+  public ASTgroup_body(int id) {
+    super(id);
+  }
+
+  public ASTgroup_body(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java b/src/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java
new file mode 100644
index 0000000..5c244fa
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTlocal_part.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTlocal_part.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTlocal_part extends SimpleNode {
+  public ASTlocal_part(int id) {
+    super(id);
+  }
+
+  public ASTlocal_part(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTmailbox.java b/src/org/apache/james/mime4j/field/address/parser/ASTmailbox.java
new file mode 100644
index 0000000..aeb469d
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTmailbox.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTmailbox.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTmailbox extends SimpleNode {
+  public ASTmailbox(int id) {
+    super(id);
+  }
+
+  public ASTmailbox(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTname_addr.java b/src/org/apache/james/mime4j/field/address/parser/ASTname_addr.java
new file mode 100644
index 0000000..846c731
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTname_addr.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTname_addr.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTname_addr extends SimpleNode {
+  public ASTname_addr(int id) {
+    super(id);
+  }
+
+  public ASTname_addr(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTphrase.java b/src/org/apache/james/mime4j/field/address/parser/ASTphrase.java
new file mode 100644
index 0000000..7d711c5
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTphrase.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTphrase.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTphrase extends SimpleNode {
+  public ASTphrase(int id) {
+    super(id);
+  }
+
+  public ASTphrase(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ASTroute.java b/src/org/apache/james/mime4j/field/address/parser/ASTroute.java
new file mode 100644
index 0000000..54ea115
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ASTroute.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. ASTroute.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class ASTroute extends SimpleNode {
+  public ASTroute(int id) {
+    super(id);
+  }
+
+  public ASTroute(AddressListParser p, int id) {
+    super(p, id);
+  }
+
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParser.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParser.java
new file mode 100644
index 0000000..8094df0
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParser.java
@@ -0,0 +1,977 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParser.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.address.parser;
+
+public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeConstants, AddressListParserConstants {/*@bgen(jjtree)*/
+  protected JJTAddressListParserState jjtree = new JJTAddressListParserState();public static void main(String args[]) throws ParseException {
+                while (true) {
+                    try {
+                                AddressListParser parser = new AddressListParser(System.in);
+                        parser.parseLine();
+                        ((SimpleNode)parser.jjtree.rootNode()).dump("> ");
+                    } catch (Exception x) {
+                                x.printStackTrace();
+                                return;
+                    }
+                }
+    }
+
+    private static void log(String msg) {
+        System.out.print(msg);
+    }
+
+    public ASTaddress_list parse() throws ParseException {
+        try {
+            parseAll();
+            return (ASTaddress_list)jjtree.rootNode();
+        } catch (TokenMgrError tme) {
+            throw new ParseException(tme.getMessage());
+        }
+    }
+
+
+    void jjtreeOpenNodeScope(Node n) {
+        ((SimpleNode)n).firstToken = getToken(1);
+    }
+
+    void jjtreeCloseNodeScope(Node n) {
+        ((SimpleNode)n).lastToken = getToken(0);
+    }
+
+  final public void parseLine() throws ParseException {
+    address_list();
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case 1:
+      jj_consume_token(1);
+      break;
+    default:
+      jj_la1[0] = jj_gen;
+      ;
+    }
+    jj_consume_token(2);
+  }
+
+  final public void parseAll() throws ParseException {
+    address_list();
+    jj_consume_token(0);
+  }
+
+  final public void address_list() throws ParseException {
+ /*@bgen(jjtree) address_list */
+  ASTaddress_list jjtn000 = new ASTaddress_list(JJTADDRESS_LIST);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case 6:
+      case DOTATOM:
+      case QUOTEDSTRING:
+        address();
+        break;
+      default:
+        jj_la1[1] = jj_gen;
+        ;
+      }
+      label_1:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 3:
+          ;
+          break;
+        default:
+          jj_la1[2] = jj_gen;
+          break label_1;
+        }
+        jj_consume_token(3);
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 6:
+        case DOTATOM:
+        case QUOTEDSTRING:
+          address();
+          break;
+        default:
+          jj_la1[3] = jj_gen;
+          ;
+        }
+      }
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void address() throws ParseException {
+ /*@bgen(jjtree) address */
+  ASTaddress jjtn000 = new ASTaddress(JJTADDRESS);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      if (jj_2_1(2147483647)) {
+        addr_spec();
+      } else {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 6:
+          angle_addr();
+          break;
+        case DOTATOM:
+        case QUOTEDSTRING:
+          phrase();
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 4:
+            group_body();
+            break;
+          case 6:
+            angle_addr();
+            break;
+          default:
+            jj_la1[4] = jj_gen;
+            jj_consume_token(-1);
+            throw new ParseException();
+          }
+          break;
+        default:
+          jj_la1[5] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+      }
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void mailbox() throws ParseException {
+ /*@bgen(jjtree) mailbox */
+  ASTmailbox jjtn000 = new ASTmailbox(JJTMAILBOX);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      if (jj_2_2(2147483647)) {
+        addr_spec();
+      } else {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 6:
+          angle_addr();
+          break;
+        case DOTATOM:
+        case QUOTEDSTRING:
+          name_addr();
+          break;
+        default:
+          jj_la1[6] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+      }
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void name_addr() throws ParseException {
+ /*@bgen(jjtree) name_addr */
+  ASTname_addr jjtn000 = new ASTname_addr(JJTNAME_ADDR);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      phrase();
+      angle_addr();
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void group_body() throws ParseException {
+ /*@bgen(jjtree) group_body */
+  ASTgroup_body jjtn000 = new ASTgroup_body(JJTGROUP_BODY);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      jj_consume_token(4);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case 6:
+      case DOTATOM:
+      case QUOTEDSTRING:
+        mailbox();
+        break;
+      default:
+        jj_la1[7] = jj_gen;
+        ;
+      }
+      label_2:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 3:
+          ;
+          break;
+        default:
+          jj_la1[8] = jj_gen;
+          break label_2;
+        }
+        jj_consume_token(3);
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 6:
+        case DOTATOM:
+        case QUOTEDSTRING:
+          mailbox();
+          break;
+        default:
+          jj_la1[9] = jj_gen;
+          ;
+        }
+      }
+      jj_consume_token(5);
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void angle_addr() throws ParseException {
+ /*@bgen(jjtree) angle_addr */
+  ASTangle_addr jjtn000 = new ASTangle_addr(JJTANGLE_ADDR);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      jj_consume_token(6);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case 8:
+        route();
+        break;
+      default:
+        jj_la1[10] = jj_gen;
+        ;
+      }
+      addr_spec();
+      jj_consume_token(7);
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void route() throws ParseException {
+ /*@bgen(jjtree) route */
+  ASTroute jjtn000 = new ASTroute(JJTROUTE);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      jj_consume_token(8);
+      domain();
+      label_3:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 3:
+        case 8:
+          ;
+          break;
+        default:
+          jj_la1[11] = jj_gen;
+          break label_3;
+        }
+        label_4:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 3:
+            ;
+            break;
+          default:
+            jj_la1[12] = jj_gen;
+            break label_4;
+          }
+          jj_consume_token(3);
+        }
+        jj_consume_token(8);
+        domain();
+      }
+      jj_consume_token(4);
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void phrase() throws ParseException {
+ /*@bgen(jjtree) phrase */
+  ASTphrase jjtn000 = new ASTphrase(JJTPHRASE);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      label_5:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case DOTATOM:
+          jj_consume_token(DOTATOM);
+          break;
+        case QUOTEDSTRING:
+          jj_consume_token(QUOTEDSTRING);
+          break;
+        default:
+          jj_la1[13] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case DOTATOM:
+        case QUOTEDSTRING:
+          ;
+          break;
+        default:
+          jj_la1[14] = jj_gen;
+          break label_5;
+        }
+      }
+    } finally {
+  if (jjtc000) {
+    jjtree.closeNodeScope(jjtn000, true);
+    jjtreeCloseNodeScope(jjtn000);
+  }
+    }
+  }
+
+  final public void addr_spec() throws ParseException {
+ /*@bgen(jjtree) addr_spec */
+  ASTaddr_spec jjtn000 = new ASTaddr_spec(JJTADDR_SPEC);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+    try {
+      local_part();
+      jj_consume_token(8);
+      domain();
+    } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            {if (true) throw (RuntimeException)jjte000;}
+          }
+          if (jjte000 instanceof ParseException) {
+            {if (true) throw (ParseException)jjte000;}
+          }
+          {if (true) throw (Error)jjte000;}
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void local_part() throws ParseException {
+ /*@bgen(jjtree) local_part */
+  ASTlocal_part jjtn000 = new ASTlocal_part(JJTLOCAL_PART);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);Token t;
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case DOTATOM:
+        t = jj_consume_token(DOTATOM);
+        break;
+      case QUOTEDSTRING:
+        t = jj_consume_token(QUOTEDSTRING);
+        break;
+      default:
+        jj_la1[15] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+      label_6:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 9:
+        case DOTATOM:
+        case QUOTEDSTRING:
+          ;
+          break;
+        default:
+          jj_la1[16] = jj_gen;
+          break label_6;
+        }
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 9:
+          t = jj_consume_token(9);
+          break;
+        default:
+          jj_la1[17] = jj_gen;
+          ;
+        }
+                        if (t.image.charAt(t.image.length() - 1) != '.' || t.kind == AddressListParserConstants.QUOTEDSTRING)
+                                {if (true) throw new ParseException("Words in local part must be separated by '.'");}
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case DOTATOM:
+          t = jj_consume_token(DOTATOM);
+          break;
+        case QUOTEDSTRING:
+          t = jj_consume_token(QUOTEDSTRING);
+          break;
+        default:
+          jj_la1[18] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+      }
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final public void domain() throws ParseException {
+ /*@bgen(jjtree) domain */
+  ASTdomain jjtn000 = new ASTdomain(JJTDOMAIN);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);Token t;
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case DOTATOM:
+        t = jj_consume_token(DOTATOM);
+        label_7:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 9:
+          case DOTATOM:
+            ;
+            break;
+          default:
+            jj_la1[19] = jj_gen;
+            break label_7;
+          }
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 9:
+            t = jj_consume_token(9);
+            break;
+          default:
+            jj_la1[20] = jj_gen;
+            ;
+          }
+                                if (t.image.charAt(t.image.length() - 1) != '.')
+                                        {if (true) throw new ParseException("Atoms in domain names must be separated by '.'");}
+          t = jj_consume_token(DOTATOM);
+        }
+        break;
+      case DOMAINLITERAL:
+        jj_consume_token(DOMAINLITERAL);
+        break;
+      default:
+        jj_la1[21] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+    }
+  }
+
+  final private boolean jj_2_1(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    try { return !jj_3_1(); }
+    catch(LookaheadSuccess ls) { return true; }
+    finally { jj_save(0, xla); }
+  }
+
+  final private boolean jj_2_2(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    try { return !jj_3_2(); }
+    catch(LookaheadSuccess ls) { return true; }
+    finally { jj_save(1, xla); }
+  }
+
+  final private boolean jj_3R_11() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(9)) jj_scanpos = xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(14)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(31)) return true;
+    }
+    return false;
+  }
+
+  final private boolean jj_3R_13() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(9)) jj_scanpos = xsp;
+    if (jj_scan_token(DOTATOM)) return true;
+    return false;
+  }
+
+  final private boolean jj_3R_8() {
+    if (jj_3R_9()) return true;
+    if (jj_scan_token(8)) return true;
+    if (jj_3R_10()) return true;
+    return false;
+  }
+
+  final private boolean jj_3_1() {
+    if (jj_3R_8()) return true;
+    return false;
+  }
+
+  final private boolean jj_3R_12() {
+    if (jj_scan_token(DOTATOM)) return true;
+    Token xsp;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_13()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  final private boolean jj_3R_10() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_12()) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(18)) return true;
+    }
+    return false;
+  }
+
+  final private boolean jj_3_2() {
+    if (jj_3R_8()) return true;
+    return false;
+  }
+
+  final private boolean jj_3R_9() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_scan_token(14)) {
+    jj_scanpos = xsp;
+    if (jj_scan_token(31)) return true;
+    }
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_11()) { jj_scanpos = xsp; break; }
+    }
+    return false;
+  }
+
+  public AddressListParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  public Token token, jj_nt;
+  private int jj_ntk;
+  private Token jj_scanpos, jj_lastpos;
+  private int jj_la;
+  public boolean lookingAhead = false;
+  private boolean jj_semLA;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[22];
+  static private int[] jj_la1_0;
+  static private int[] jj_la1_1;
+  static {
+      jj_la1_0();
+      jj_la1_1();
+   }
+   private static void jj_la1_0() {
+      jj_la1_0 = new int[] {0x2,0x80004040,0x8,0x80004040,0x50,0x80004040,0x80004040,0x80004040,0x8,0x80004040,0x100,0x108,0x8,0x80004000,0x80004000,0x80004000,0x80004200,0x200,0x80004000,0x4200,0x200,0x44000,};
+   }
+   private static void jj_la1_1() {
+      jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,};
+   }
+  final private JJCalls[] jj_2_rtns = new JJCalls[2];
+  private boolean jj_rescan = false;
+  private int jj_gc = 0;
+
+  public AddressListParser(java.io.InputStream stream) {
+     this(stream, null);
+  }
+  public AddressListParser(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source = new AddressListParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.InputStream stream) {
+     ReInit(stream, null);
+  }
+  public void ReInit(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public AddressListParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new AddressListParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public AddressListParser(AddressListParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(AddressListParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jjtree.reset();
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  final private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      if (++jj_gc > 100) {
+        jj_gc = 0;
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+          JJCalls c = jj_2_rtns[i];
+          while (c != null) {
+            if (c.gen < jj_gen) c.first = null;
+            c = c.next;
+          }
+        }
+      }
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  static private final class LookaheadSuccess extends java.lang.Error { }
+  final private LookaheadSuccess jj_ls = new LookaheadSuccess();
+  final private boolean jj_scan_token(int kind) {
+    if (jj_scanpos == jj_lastpos) {
+      jj_la--;
+      if (jj_scanpos.next == null) {
+        jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken();
+      } else {
+        jj_lastpos = jj_scanpos = jj_scanpos.next;
+      }
+    } else {
+      jj_scanpos = jj_scanpos.next;
+    }
+    if (jj_rescan) {
+      int i = 0; Token tok = token;
+      while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
+      if (tok != null) jj_add_error_token(kind, i);
+    }
+    if (jj_scanpos.kind != kind) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls;
+    return false;
+  }
+
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+  final public Token getToken(int index) {
+    Token t = lookingAhead ? jj_scanpos : token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  final private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private java.util.Vector<int[]> jj_expentries = new java.util.Vector<int[]>();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+  private int[] jj_lasttokens = new int[100];
+  private int jj_endpos;
+
+  private void jj_add_error_token(int kind, int pos) {
+    if (pos >= 100) return;
+    if (pos == jj_endpos + 1) {
+      jj_lasttokens[jj_endpos++] = kind;
+    } else if (jj_endpos != 0) {
+      jj_expentry = new int[jj_endpos];
+      for (int i = 0; i < jj_endpos; i++) {
+        jj_expentry[i] = jj_lasttokens[i];
+      }
+      boolean exists = false;
+      for (java.util.Enumeration<int[]> e = jj_expentries.elements(); e.hasMoreElements();) {
+        int[] oldentry = e.nextElement();
+        if (oldentry.length == jj_expentry.length) {
+          exists = true;
+          for (int i = 0; i < jj_expentry.length; i++) {
+            if (oldentry[i] != jj_expentry[i]) {
+              exists = false;
+              break;
+            }
+          }
+          if (exists) break;
+        }
+      }
+      if (!exists) jj_expentries.addElement(jj_expentry);
+      if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+    }
+  }
+
+  public ParseException generateParseException() {
+    jj_expentries.removeAllElements();
+    boolean[] la1tokens = new boolean[34];
+    for (int i = 0; i < 34; i++) {
+      la1tokens[i] = false;
+    }
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 22; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+          if ((jj_la1_1[i] & (1<<j)) != 0) {
+            la1tokens[32+j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 34; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.addElement(jj_expentry);
+      }
+    }
+    jj_endpos = 0;
+    jj_rescan_token();
+    jj_add_error_token(0, 0);
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = jj_expentries.elementAt(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  final public void enable_tracing() {
+  }
+
+  final public void disable_tracing() {
+  }
+
+  final private void jj_rescan_token() {
+    jj_rescan = true;
+    for (int i = 0; i < 2; i++) {
+    try {
+      JJCalls p = jj_2_rtns[i];
+      do {
+        if (p.gen > jj_gen) {
+          jj_la = p.arg; jj_lastpos = jj_scanpos = p.first;
+          switch (i) {
+            case 0: jj_3_1(); break;
+            case 1: jj_3_2(); break;
+          }
+        }
+        p = p.next;
+      } while (p != null);
+      } catch(LookaheadSuccess ls) { }
+    }
+    jj_rescan = false;
+  }
+
+  final private void jj_save(int index, int xla) {
+    JJCalls p = jj_2_rtns[index];
+    while (p.gen > jj_gen) {
+      if (p.next == null) { p = p.next = new JJCalls(); break; }
+      p = p.next;
+    }
+    p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla;
+  }
+
+  static final class JJCalls {
+    int gen;
+    Token first;
+    int arg;
+    JJCalls next;
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParser.jj b/src/org/apache/james/mime4j/field/address/parser/AddressListParser.jj
new file mode 100644
index 0000000..c14277b
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParser.jj
@@ -0,0 +1,595 @@
+/*@bgen(jjtree) Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParser.jj */
+/*@egen*//****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+
+/**
+ * RFC2822 address list parser.
+ *
+ * Created 9/17/2004
+ * by Joe Cheng <[email protected]>
+ */
+
+options {
+	STATIC=false;
+	LOOKAHEAD=1;                                                                                                                               
+	//DEBUG_PARSER=true;
+	//DEBUG_TOKEN_MANAGER=true;
+}
+
+PARSER_BEGIN(AddressListParser)
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.address.parser;
+
+public class AddressListParser/*@bgen(jjtree)*/implements AddressListParserTreeConstants/*@egen*/ {/*@bgen(jjtree)*/
+  protected JJTAddressListParserState jjtree = new JJTAddressListParserState();
+
+/*@egen*/
+    public static void main(String args[]) throws ParseException {
+		while (true) {
+		    try {
+				AddressListParser parser = new AddressListParser(System.in);
+		    	parser.parseLine();
+		    	((SimpleNode)parser.jjtree.rootNode()).dump("> ");
+		    } catch (Exception x) {
+				x.printStackTrace();
+				return;
+		    }
+		}
+    }
+    
+    private static void log(String msg) {
+    	System.out.print(msg);
+    }
+    
+    public ASTaddress_list parse() throws ParseException {
+        try {
+    	    parseAll();
+    	    return (ASTaddress_list)jjtree.rootNode();
+    	} catch (TokenMgrError tme) {
+    	    throw new ParseException(tme.getMessage());
+    	}
+    }
+    
+    
+    void jjtreeOpenNodeScope(Node n) {
+    	((SimpleNode)n).firstToken = getToken(1);
+    }
+    
+    void jjtreeCloseNodeScope(Node n) {
+    	((SimpleNode)n).lastToken = getToken(0);
+    }
+}
+
+PARSER_END(AddressListParser)
+
+void parseLine()       :
+{}
+{
+	address_list() ["\r"] "\n"
+}
+
+void parseAll()       :
+{}
+{
+	address_list() <EOF>
+}
+
+void address_list() :
+{/*@bgen(jjtree) address_list */
+  ASTaddress_list jjtn000 = new ASTaddress_list(JJTADDRESS_LIST);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) address_list */
+        try {
+/*@egen*/
+	[ address() ]
+	(
+		","
+		[ address() ]
+	)*/*@bgen(jjtree)*/
+        } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            throw (RuntimeException)jjte000;
+          }
+          if (jjte000 instanceof ParseException) {
+            throw (ParseException)jjte000;
+          }
+          throw (Error)jjte000;
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void address() :
+{/*@bgen(jjtree) address */
+  ASTaddress jjtn000 = new ASTaddress(JJTADDRESS);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) address */
+        try {
+/*@egen*/
+	LOOKAHEAD(2147483647)
+	addr_spec()
+|	angle_addr()
+|	( phrase() (group_body() | angle_addr()) )/*@bgen(jjtree)*/
+        } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            throw (RuntimeException)jjte000;
+          }
+          if (jjte000 instanceof ParseException) {
+            throw (ParseException)jjte000;
+          }
+          throw (Error)jjte000;
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void mailbox() :
+{/*@bgen(jjtree) mailbox */
+  ASTmailbox jjtn000 = new ASTmailbox(JJTMAILBOX);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) mailbox */
+        try {
+/*@egen*/
+	LOOKAHEAD(2147483647)
+	addr_spec()
+|	angle_addr()
+|	name_addr()/*@bgen(jjtree)*/
+        } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            throw (RuntimeException)jjte000;
+          }
+          if (jjte000 instanceof ParseException) {
+            throw (ParseException)jjte000;
+          }
+          throw (Error)jjte000;
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void name_addr() :
+{/*@bgen(jjtree) name_addr */
+  ASTname_addr jjtn000 = new ASTname_addr(JJTNAME_ADDR);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) name_addr */
+        try {
+/*@egen*/
+	phrase() angle_addr()/*@bgen(jjtree)*/
+        } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            throw (RuntimeException)jjte000;
+          }
+          if (jjte000 instanceof ParseException) {
+            throw (ParseException)jjte000;
+          }
+          throw (Error)jjte000;
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void group_body() :
+{/*@bgen(jjtree) group_body */
+  ASTgroup_body jjtn000 = new ASTgroup_body(JJTGROUP_BODY);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) group_body */
+        try {
+/*@egen*/
+	":"
+	[ mailbox() ]
+	(
+		","
+		[ mailbox() ]
+	)*
+	";"/*@bgen(jjtree)*/
+        } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            throw (RuntimeException)jjte000;
+          }
+          if (jjte000 instanceof ParseException) {
+            throw (ParseException)jjte000;
+          }
+          throw (Error)jjte000;
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void angle_addr() :
+{/*@bgen(jjtree) angle_addr */
+  ASTangle_addr jjtn000 = new ASTangle_addr(JJTANGLE_ADDR);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) angle_addr */
+        try {
+/*@egen*/
+	"<" [ route() ] addr_spec() ">"/*@bgen(jjtree)*/
+        } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            throw (RuntimeException)jjte000;
+          }
+          if (jjte000 instanceof ParseException) {
+            throw (ParseException)jjte000;
+          }
+          throw (Error)jjte000;
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void route() :
+{/*@bgen(jjtree) route */
+  ASTroute jjtn000 = new ASTroute(JJTROUTE);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) route */
+        try {
+/*@egen*/
+	"@" domain() ( (",")* "@" domain() )* ":"/*@bgen(jjtree)*/
+        } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            throw (RuntimeException)jjte000;
+          }
+          if (jjte000 instanceof ParseException) {
+            throw (ParseException)jjte000;
+          }
+          throw (Error)jjte000;
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void phrase() :
+{/*@bgen(jjtree) phrase */
+  ASTphrase jjtn000 = new ASTphrase(JJTPHRASE);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) phrase */
+try {
+/*@egen*/
+(	<DOTATOM>
+|	<QUOTEDSTRING>
+)+/*@bgen(jjtree)*/
+} finally {
+  if (jjtc000) {
+    jjtree.closeNodeScope(jjtn000, true);
+    jjtreeCloseNodeScope(jjtn000);
+  }
+}
+/*@egen*/
+}
+
+void addr_spec() :
+{/*@bgen(jjtree) addr_spec */
+  ASTaddr_spec jjtn000 = new ASTaddr_spec(JJTADDR_SPEC);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/}
+{/*@bgen(jjtree) addr_spec */
+        try {
+/*@egen*/
+	( local_part() "@" domain() )/*@bgen(jjtree)*/
+        } catch (Throwable jjte000) {
+          if (jjtc000) {
+            jjtree.clearNodeScope(jjtn000);
+            jjtc000 = false;
+          } else {
+            jjtree.popNode();
+          }
+          if (jjte000 instanceof RuntimeException) {
+            throw (RuntimeException)jjte000;
+          }
+          if (jjte000 instanceof ParseException) {
+            throw (ParseException)jjte000;
+          }
+          throw (Error)jjte000;
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void local_part() :
+{/*@bgen(jjtree) local_part */
+  ASTlocal_part jjtn000 = new ASTlocal_part(JJTLOCAL_PART);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/ Token t; }
+{/*@bgen(jjtree) local_part */
+        try {
+/*@egen*/
+	( t=<DOTATOM> | t=<QUOTEDSTRING> )
+	(	[t="."]
+		{
+			if (t.image.charAt(t.image.length() - 1) != '.' || t.kind == AddressListParserConstants.QUOTEDSTRING)
+				throw new ParseException("Words in local part must be separated by '.'");
+		}
+		(	t=<DOTATOM> | t=<QUOTEDSTRING> )
+	)*/*@bgen(jjtree)*/
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+void domain() :
+{/*@bgen(jjtree) domain */
+  ASTdomain jjtn000 = new ASTdomain(JJTDOMAIN);
+  boolean jjtc000 = true;
+  jjtree.openNodeScope(jjtn000);
+  jjtreeOpenNodeScope(jjtn000);
+/*@egen*/ Token t; }
+{/*@bgen(jjtree) domain */
+        try {
+/*@egen*/
+	(	t=<DOTATOM>
+		(	[t="."]
+			{
+				if (t.image.charAt(t.image.length() - 1) != '.')
+					throw new ParseException("Atoms in domain names must be separated by '.'");
+			}
+			t=<DOTATOM>
+		)*
+	)
+|	<DOMAINLITERAL>/*@bgen(jjtree)*/
+        } finally {
+          if (jjtc000) {
+            jjtree.closeNodeScope(jjtn000, true);
+            jjtreeCloseNodeScope(jjtn000);
+          }
+        }
+/*@egen*/
+}
+
+SPECIAL_TOKEN :
+{
+ 	< WS: ( [" ", "\t"] )+ >
+}
+
+TOKEN :
+{
+	< #ALPHA: ["a" - "z", "A" - "Z"] >
+|	< #DIGIT: ["0" - "9"] >
+|	< #ATEXT: ( <ALPHA> | <DIGIT>
+			  | "!" | "#" | "$" | "%"
+			  | "&" | "'" | "*" | "+"
+			  | "-" | "/" | "=" | "?"
+			  | "^" | "_" | "`" | "{"
+			  | "|" | "}" | "~"
+			  )>
+|	< DOTATOM: <ATEXT> ( <ATEXT> | "." )* >
+}
+
+TOKEN_MGR_DECLS :
+{
+	// Keeps track of how many levels of comment nesting
+	// we've encountered.  This is only used when the 2nd
+	// level is reached, for example ((this)), not (this).
+	// This is because the outermost level must be treated
+	// specially anyway, because the outermost ")" has a 
+	// different token type than inner ")" instances.
+	static int commentNest;
+}
+
+MORE :
+{
+	// domain literal
+	"[" : INDOMAINLITERAL
+}
+
+<INDOMAINLITERAL>
+MORE :
+{
+	< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
+|	< ~["[", "]", "\\"] >
+}
+
+<INDOMAINLITERAL>
+TOKEN :
+{
+	< DOMAINLITERAL: "]" > { matchedToken.image = image.toString(); }: DEFAULT
+}
+
+MORE :
+{
+	// starts a comment
+	"(" : INCOMMENT
+}
+
+<INCOMMENT>
+SKIP :
+{
+	// ends a comment
+	< COMMENT: ")" > : DEFAULT
+	// if this is ever changed to not be a SKIP, need
+	// to make sure matchedToken.token = token.toString()
+	// is called.
+}
+
+<INCOMMENT>
+MORE :
+{
+	< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
+|	"(" { commentNest = 1; } : NESTED_COMMENT
+|	< <ANY>>
+}
+
+<NESTED_COMMENT>
+MORE :
+{
+	< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
+|	"(" { ++commentNest; }
+|	")" { --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT); }
+|	< <ANY>>
+}
+
+
+// QUOTED STRINGS
+
+MORE :
+{
+	"\"" { image.deleteCharAt(image.length() - 1); } : INQUOTEDSTRING
+}
+
+<INQUOTEDSTRING>
+MORE :
+{
+	< <QUOTEDPAIR>> { image.deleteCharAt(image.length() - 2); }
+|	< (~["\"", "\\"])+ >
+}
+
+<INQUOTEDSTRING>
+TOKEN :
+{
+	< QUOTEDSTRING: "\"" > { matchedToken.image = image.substring(0, image.length() - 1); } : DEFAULT
+}
+
+// GLOBALS
+
+<*>
+TOKEN :
+{
+	< #QUOTEDPAIR: "\\" <ANY> >
+|	< #ANY: ~[] >
+}
+
+// ERROR!
+/*
+
+<*>
+TOKEN :
+{
+	< UNEXPECTED_CHAR: <ANY> >
+}
+
+*/
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java
new file mode 100644
index 0000000..006a082
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParserConstants.java
@@ -0,0 +1,76 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParserConstants.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.address.parser;
+
+public interface AddressListParserConstants {
+
+  int EOF = 0;
+  int WS = 10;
+  int ALPHA = 11;
+  int DIGIT = 12;
+  int ATEXT = 13;
+  int DOTATOM = 14;
+  int DOMAINLITERAL = 18;
+  int COMMENT = 20;
+  int QUOTEDSTRING = 31;
+  int QUOTEDPAIR = 32;
+  int ANY = 33;
+
+  int DEFAULT = 0;
+  int INDOMAINLITERAL = 1;
+  int INCOMMENT = 2;
+  int NESTED_COMMENT = 3;
+  int INQUOTEDSTRING = 4;
+
+  String[] tokenImage = {
+    "<EOF>",
+    "\"\\r\"",
+    "\"\\n\"",
+    "\",\"",
+    "\":\"",
+    "\";\"",
+    "\"<\"",
+    "\">\"",
+    "\"@\"",
+    "\".\"",
+    "<WS>",
+    "<ALPHA>",
+    "<DIGIT>",
+    "<ATEXT>",
+    "<DOTATOM>",
+    "\"[\"",
+    "<token of kind 16>",
+    "<token of kind 17>",
+    "\"]\"",
+    "\"(\"",
+    "\")\"",
+    "<token of kind 21>",
+    "\"(\"",
+    "<token of kind 23>",
+    "<token of kind 24>",
+    "\"(\"",
+    "\")\"",
+    "<token of kind 27>",
+    "\"\\\"\"",
+    "<token of kind 29>",
+    "<token of kind 30>",
+    "\"\\\"\"",
+    "<QUOTEDPAIR>",
+    "<ANY>",
+  };
+
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java
new file mode 100644
index 0000000..d2dd88d
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParserTokenManager.java
@@ -0,0 +1,1009 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. AddressListParserTokenManager.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.address.parser;
+
+public class AddressListParserTokenManager implements AddressListParserConstants
+{
+        // Keeps track of how many levels of comment nesting
+        // we've encountered.  This is only used when the 2nd
+        // level is reached, for example ((this)), not (this).
+        // This is because the outermost level must be treated
+        // specially anyway, because the outermost ")" has a 
+        // different token type than inner ")" instances.
+        static int commentNest;
+  public  java.io.PrintStream debugStream = System.out;
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private final int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private final int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 10:
+         return jjStopAtPos(0, 2);
+      case 13:
+         return jjStopAtPos(0, 1);
+      case 34:
+         return jjStopAtPos(0, 28);
+      case 40:
+         return jjStopAtPos(0, 19);
+      case 44:
+         return jjStopAtPos(0, 3);
+      case 46:
+         return jjStopAtPos(0, 9);
+      case 58:
+         return jjStopAtPos(0, 4);
+      case 59:
+         return jjStopAtPos(0, 5);
+      case 60:
+         return jjStopAtPos(0, 6);
+      case 62:
+         return jjStopAtPos(0, 7);
+      case 64:
+         return jjStopAtPos(0, 8);
+      case 91:
+         return jjStopAtPos(0, 15);
+      default :
+         return jjMoveNfa_0(1, 0);
+   }
+}
+private final void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private final void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private final void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+private final void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+private final void jjCheckNAddStates(int start)
+{
+   jjCheckNAdd(jjnextStates[start]);
+   jjCheckNAdd(jjnextStates[start + 1]);
+}
+private final int jjMoveNfa_0(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 1:
+                  if ((0xa3ffacfa00000000L & l) != 0L)
+                  {
+                     if (kind > 14)
+                        kind = 14;
+                     jjCheckNAdd(2);
+                  }
+                  else if ((0x100000200L & l) != 0L)
+                  {
+                     if (kind > 10)
+                        kind = 10;
+                     jjCheckNAdd(0);
+                  }
+                  break;
+               case 0:
+                  if ((0x100000200L & l) == 0L)
+                     break;
+                  kind = 10;
+                  jjCheckNAdd(0);
+                  break;
+               case 2:
+                  if ((0xa3ffecfa00000000L & l) == 0L)
+                     break;
+                  if (kind > 14)
+                     kind = 14;
+                  jjCheckNAdd(2);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 1:
+               case 2:
+                  if ((0x7fffffffc7fffffeL & l) == 0L)
+                     break;
+                  if (kind > 14)
+                     kind = 14;
+                  jjCheckNAdd(2);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_2(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_2(int pos, long active0)
+{
+   return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_2(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_2(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_2()
+{
+   switch(curChar)
+   {
+      case 40:
+         return jjStopAtPos(0, 22);
+      case 41:
+         return jjStopAtPos(0, 20);
+      default :
+         return jjMoveNfa_2(0, 0);
+   }
+}
+static final long[] jjbitVec0 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private final int jjMoveNfa_2(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 23)
+                     kind = 23;
+                  break;
+               case 1:
+                  if (kind > 21)
+                     kind = 21;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 23)
+                     kind = 23;
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 21)
+                     kind = 21;
+                  break;
+               case 2:
+                  if (kind > 23)
+                     kind = 23;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 23)
+                     kind = 23;
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 21)
+                     kind = 21;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_4(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_4(int pos, long active0)
+{
+   return jjMoveNfa_4(jjStopStringLiteralDfa_4(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_4(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_4(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_4()
+{
+   switch(curChar)
+   {
+      case 34:
+         return jjStopAtPos(0, 31);
+      default :
+         return jjMoveNfa_4(0, 0);
+   }
+}
+private final int jjMoveNfa_4(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+               case 2:
+                  if ((0xfffffffbffffffffL & l) == 0L)
+                     break;
+                  if (kind > 30)
+                     kind = 30;
+                  jjCheckNAdd(2);
+                  break;
+               case 1:
+                  if (kind > 29)
+                     kind = 29;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                  {
+                     if (kind > 30)
+                        kind = 30;
+                     jjCheckNAdd(2);
+                  }
+                  else if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 29)
+                     kind = 29;
+                  break;
+               case 2:
+                  if ((0xffffffffefffffffL & l) == 0L)
+                     break;
+                  if (kind > 30)
+                     kind = 30;
+                  jjCheckNAdd(2);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+               case 2:
+                  if ((jjbitVec0[i2] & l2) == 0L)
+                     break;
+                  if (kind > 30)
+                     kind = 30;
+                  jjCheckNAdd(2);
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 29)
+                     kind = 29;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_3(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_3(int pos, long active0)
+{
+   return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_3(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_3(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_3()
+{
+   switch(curChar)
+   {
+      case 40:
+         return jjStopAtPos(0, 25);
+      case 41:
+         return jjStopAtPos(0, 26);
+      default :
+         return jjMoveNfa_3(0, 0);
+   }
+}
+private final int jjMoveNfa_3(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 27)
+                     kind = 27;
+                  break;
+               case 1:
+                  if (kind > 24)
+                     kind = 24;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 27)
+                     kind = 27;
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 24)
+                     kind = 24;
+                  break;
+               case 2:
+                  if (kind > 27)
+                     kind = 27;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 27)
+                     kind = 27;
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 24)
+                     kind = 24;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_1(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_1(int pos, long active0)
+{
+   return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_1(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_1(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_1()
+{
+   switch(curChar)
+   {
+      case 93:
+         return jjStopAtPos(0, 18);
+      default :
+         return jjMoveNfa_1(0, 0);
+   }
+}
+private final int jjMoveNfa_1(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 17)
+                     kind = 17;
+                  break;
+               case 1:
+                  if (kind > 16)
+                     kind = 16;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0xffffffffc7ffffffL & l) != 0L)
+                  {
+                     if (kind > 17)
+                        kind = 17;
+                  }
+                  else if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 16)
+                     kind = 16;
+                  break;
+               case 2:
+                  if ((0xffffffffc7ffffffL & l) != 0L && kind > 17)
+                     kind = 17;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 17)
+                     kind = 17;
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 16)
+                     kind = 16;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+};
+public static final String[] jjstrLiteralImages = {
+"", "\15", "\12", "\54", "\72", "\73", "\74", "\76", "\100", "\56", null, null, 
+null, null, null, null, null, null, null, null, null, null, null, null, null, null, 
+null, null, null, null, null, null, null, null, };
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+   "INDOMAINLITERAL", 
+   "INCOMMENT", 
+   "NESTED_COMMENT", 
+   "INQUOTEDSTRING", 
+};
+public static final int[] jjnewLexState = {
+   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 0, 2, 0, -1, 3, -1, -1, 
+   -1, -1, -1, 4, -1, -1, 0, -1, -1, 
+};
+static final long[] jjtoToken = {
+   0x800443ffL, 
+};
+static final long[] jjtoSkip = {
+   0x100400L, 
+};
+static final long[] jjtoSpecial = {
+   0x400L, 
+};
+static final long[] jjtoMore = {
+   0x7feb8000L, 
+};
+protected SimpleCharStream input_stream;
+private final int[] jjrounds = new int[3];
+private final int[] jjstateSet = new int[6];
+StringBuffer image;
+int jjimageLen;
+int lengthOfMatch;
+protected char curChar;
+public AddressListParserTokenManager(SimpleCharStream stream){
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+public AddressListParserTokenManager(SimpleCharStream stream, int lexState){
+   this(stream);
+   SwitchTo(lexState);
+}
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private final void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 3; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 5 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+   Token t = Token.newToken(jjmatchedKind);
+   t.kind = jjmatchedKind;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   t.image = (im == null) ? input_stream.GetImage() : im;
+   t.beginLine = input_stream.getBeginLine();
+   t.beginColumn = input_stream.getBeginColumn();
+   t.endLine = input_stream.getEndLine();
+   t.endColumn = input_stream.getEndColumn();
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+public Token getNextToken() 
+{
+  int kind;
+  Token specialToken = null;
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      matchedToken.specialToken = specialToken;
+      return matchedToken;
+   }
+   image = null;
+   jjimageLen = 0;
+
+   for (;;)
+   {
+     switch(curLexState)
+     {
+       case 0:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_0();
+         break;
+       case 1:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_1();
+         break;
+       case 2:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_2();
+         break;
+       case 3:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_3();
+         break;
+       case 4:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_4();
+         break;
+     }
+     if (jjmatchedKind != 0x7fffffff)
+     {
+        if (jjmatchedPos + 1 < curPos)
+           input_stream.backup(curPos - jjmatchedPos - 1);
+        if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+        {
+           matchedToken = jjFillToken();
+           matchedToken.specialToken = specialToken;
+           TokenLexicalActions(matchedToken);
+       if (jjnewLexState[jjmatchedKind] != -1)
+         curLexState = jjnewLexState[jjmatchedKind];
+           return matchedToken;
+        }
+        else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+        {
+           if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+           {
+              matchedToken = jjFillToken();
+              if (specialToken == null)
+                 specialToken = matchedToken;
+              else
+              {
+                 matchedToken.specialToken = specialToken;
+                 specialToken = (specialToken.next = matchedToken);
+              }
+           }
+         if (jjnewLexState[jjmatchedKind] != -1)
+           curLexState = jjnewLexState[jjmatchedKind];
+           continue EOFLoop;
+        }
+        MoreLexicalActions();
+      if (jjnewLexState[jjmatchedKind] != -1)
+        curLexState = jjnewLexState[jjmatchedKind];
+        curPos = 0;
+        jjmatchedKind = 0x7fffffff;
+        try {
+           curChar = input_stream.readChar();
+           continue;
+        }
+        catch (java.io.IOException e1) { }
+     }
+     int error_line = input_stream.getEndLine();
+     int error_column = input_stream.getEndColumn();
+     String error_after = null;
+     boolean EOFSeen = false;
+     try { input_stream.readChar(); input_stream.backup(1); }
+     catch (java.io.IOException e1) {
+        EOFSeen = true;
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+        if (curChar == '\n' || curChar == '\r') {
+           error_line++;
+           error_column = 0;
+        }
+        else
+           error_column++;
+     }
+     if (!EOFSeen) {
+        input_stream.backup(1);
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+     }
+     throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+   }
+  }
+}
+
+void MoreLexicalActions()
+{
+   jjimageLen += (lengthOfMatch = jjmatchedPos + 1);
+   switch(jjmatchedKind)
+   {
+      case 16 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      case 21 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      case 22 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              commentNest = 1;
+         break;
+      case 24 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      case 25 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              ++commentNest;
+         break;
+      case 26 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT);
+         break;
+      case 28 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+               image.deleteCharAt(image.length() - 1);
+         break;
+      case 29 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      default : 
+         break;
+   }
+}
+void TokenLexicalActions(Token matchedToken)
+{
+   switch(jjmatchedKind)
+   {
+      case 18 :
+        if (image == null)
+            image = new StringBuffer();
+            image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+                                 matchedToken.image = image.toString();
+         break;
+      case 31 :
+        if (image == null)
+            image = new StringBuffer();
+            image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+                                 matchedToken.image = image.substring(0, image.length() - 1);
+         break;
+      default : 
+         break;
+   }
+}
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java
new file mode 100644
index 0000000..5987f19
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java
@@ -0,0 +1,35 @@
+/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParserTreeConstants.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public interface AddressListParserTreeConstants
+{
+  public int JJTVOID = 0;
+  public int JJTADDRESS_LIST = 1;
+  public int JJTADDRESS = 2;
+  public int JJTMAILBOX = 3;
+  public int JJTNAME_ADDR = 4;
+  public int JJTGROUP_BODY = 5;
+  public int JJTANGLE_ADDR = 6;
+  public int JJTROUTE = 7;
+  public int JJTPHRASE = 8;
+  public int JJTADDR_SPEC = 9;
+  public int JJTLOCAL_PART = 10;
+  public int JJTDOMAIN = 11;
+
+
+  public String[] jjtNodeName = {
+    "void",
+    "address_list",
+    "address",
+    "mailbox",
+    "name_addr",
+    "group_body",
+    "angle_addr",
+    "route",
+    "phrase",
+    "addr_spec",
+    "local_part",
+    "domain",
+  };
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java b/src/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java
new file mode 100644
index 0000000..8ec2fe7
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java
@@ -0,0 +1,19 @@
+/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/AddressListParserVisitor.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public interface AddressListParserVisitor
+{
+  public Object visit(SimpleNode node, Object data);
+  public Object visit(ASTaddress_list node, Object data);
+  public Object visit(ASTaddress node, Object data);
+  public Object visit(ASTmailbox node, Object data);
+  public Object visit(ASTname_addr node, Object data);
+  public Object visit(ASTgroup_body node, Object data);
+  public Object visit(ASTangle_addr node, Object data);
+  public Object visit(ASTroute node, Object data);
+  public Object visit(ASTphrase node, Object data);
+  public Object visit(ASTaddr_spec node, Object data);
+  public Object visit(ASTlocal_part node, Object data);
+  public Object visit(ASTdomain node, Object data);
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/BaseNode.java b/src/org/apache/james/mime4j/field/address/parser/BaseNode.java
new file mode 100644
index 0000000..7809746
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/BaseNode.java
@@ -0,0 +1,30 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.address.parser;
+
+import org.apache.james.mime4j.field.address.parser.Node;
+import org.apache.james.mime4j.field.address.parser.Token;
+
+public abstract class BaseNode implements Node {
+  
+  public Token firstToken;
+  public Token lastToken;
+
+}
\ No newline at end of file
diff --git a/src/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java b/src/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java
new file mode 100644
index 0000000..08b5c5b
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java
@@ -0,0 +1,123 @@
+/* Generated By:JJTree: Do not edit this line. /Users/jason/Projects/apache-mime4j-0.3/target/generated-sources/jjtree/org/apache/james/mime4j/field/address/parser/JJTAddressListParserState.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+class JJTAddressListParserState {
+  private java.util.Stack<Node> nodes;
+  private java.util.Stack<Integer> marks;
+
+  private int sp;		// number of nodes on stack
+  private int mk;		// current mark
+  private boolean node_created;
+
+  JJTAddressListParserState() {
+    nodes = new java.util.Stack<Node>();
+    marks = new java.util.Stack<Integer>();
+    sp = 0;
+    mk = 0;
+  }
+
+  /* Determines whether the current node was actually closed and
+     pushed.  This should only be called in the final user action of a
+     node scope.  */
+  boolean nodeCreated() {
+    return node_created;
+  }
+
+  /* Call this to reinitialize the node stack.  It is called
+     automatically by the parser's ReInit() method. */
+  void reset() {
+    nodes.removeAllElements();
+    marks.removeAllElements();
+    sp = 0;
+    mk = 0;
+  }
+
+  /* Returns the root node of the AST.  It only makes sense to call
+     this after a successful parse. */
+  Node rootNode() {
+    return nodes.elementAt(0);
+  }
+
+  /* Pushes a node on to the stack. */
+  void pushNode(Node n) {
+    nodes.push(n);
+    ++sp;
+  }
+
+  /* Returns the node on the top of the stack, and remove it from the
+     stack.  */
+  Node popNode() {
+    if (--sp < mk) {
+      mk = marks.pop().intValue();
+    }
+    return nodes.pop();
+  }
+
+  /* Returns the node currently on the top of the stack. */
+  Node peekNode() {
+    return nodes.peek();
+  }
+
+  /* Returns the number of children on the stack in the current node
+     scope. */
+  int nodeArity() {
+    return sp - mk;
+  }
+
+
+  void clearNodeScope(Node n) {
+    while (sp > mk) {
+      popNode();
+    }
+    mk = marks.pop().intValue();
+  }
+
+
+  void openNodeScope(Node n) {
+    marks.push(new Integer(mk));
+    mk = sp;
+    n.jjtOpen();
+  }
+
+
+  /* A definite node is constructed from a specified number of
+     children.  That number of nodes are popped from the stack and
+     made the children of the definite node.  Then the definite node
+     is pushed on to the stack. */
+  void closeNodeScope(Node n, int num) {
+    mk = marks.pop().intValue();
+    while (num-- > 0) {
+      Node c = popNode();
+      c.jjtSetParent(n);
+      n.jjtAddChild(c, num);
+    }
+    n.jjtClose();
+    pushNode(n);
+    node_created = true;
+  }
+
+
+  /* A conditional node is constructed if its condition is true.  All
+     the nodes that have been pushed since the node was opened are
+     made children of the the conditional node, which is then pushed
+     on to the stack.  If the condition is false the node is not
+     constructed and they are left on the stack. */
+  void closeNodeScope(Node n, boolean condition) {
+    if (condition) {
+      int a = nodeArity();
+      mk = marks.pop().intValue();
+      while (a-- > 0) {
+	Node c = popNode();
+	c.jjtSetParent(n);
+	n.jjtAddChild(c, a);
+      }
+      n.jjtClose();
+      pushNode(n);
+      node_created = true;
+    } else {
+      mk = marks.pop().intValue();
+      node_created = false;
+    }
+  }
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/Node.java b/src/org/apache/james/mime4j/field/address/parser/Node.java
new file mode 100644
index 0000000..1588920
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/Node.java
@@ -0,0 +1,37 @@
+/* Generated By:JJTree: Do not edit this line. Node.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+/* All AST nodes must implement this interface.  It provides basic
+   machinery for constructing the parent and child relationships
+   between nodes. */
+
+public interface Node {
+
+  /** This method is called after the node has been made the current
+    node.  It indicates that child nodes can now be added to it. */
+  public void jjtOpen();
+
+  /** This method is called after all the child nodes have been
+    added. */
+  public void jjtClose();
+
+  /** This pair of methods are used to inform the node of its
+    parent. */
+  public void jjtSetParent(Node n);
+  public Node jjtGetParent();
+
+  /** This method tells the node to add its argument to the node's
+    list of children.  */
+  public void jjtAddChild(Node n, int i);
+
+  /** This method returns a child node.  The children are numbered
+     from zero, left to right. */
+  public Node jjtGetChild(int i);
+
+  /** Return the number of children the node has. */
+  public int jjtGetNumChildren();
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data);
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/ParseException.java b/src/org/apache/james/mime4j/field/address/parser/ParseException.java
new file mode 100644
index 0000000..e20146f
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/ParseException.java
@@ -0,0 +1,207 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.address.parser;
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+  /**
+   * This constructor is used by the method "generateParseException"
+   * in the generated parser.  Calling this constructor generates
+   * a new object of this type with the fields "currentToken",
+   * "expectedTokenSequences", and "tokenImage" set.  The boolean
+   * flag "specialConstructor" is also set to true to indicate that
+   * this constructor was used to create this object.
+   * This constructor calls its super class with the empty string
+   * to force the "toString" method of parent class "Throwable" to
+   * print the error message in the form:
+   *     ParseException: <result of getMessage>
+   */
+  public ParseException(Token currentTokenVal,
+                        int[][] expectedTokenSequencesVal,
+                        String[] tokenImageVal
+                       )
+  {
+    super("");
+    specialConstructor = true;
+    currentToken = currentTokenVal;
+    expectedTokenSequences = expectedTokenSequencesVal;
+    tokenImage = tokenImageVal;
+  }
+
+  /**
+   * The following constructors are for use by you for whatever
+   * purpose you can think of.  Constructing the exception in this
+   * manner makes the exception behave in the normal way - i.e., as
+   * documented in the class "Throwable".  The fields "errorToken",
+   * "expectedTokenSequences", and "tokenImage" do not contain
+   * relevant information.  The JavaCC generated code does not use
+   * these constructors.
+   */
+
+  public ParseException() {
+    super();
+    specialConstructor = false;
+  }
+
+  public ParseException(String message) {
+    super(message);
+    specialConstructor = false;
+  }
+
+  /**
+   * This variable determines which constructor was used to create
+   * this object and thereby affects the semantics of the
+   * "getMessage" method (see below).
+   */
+  protected boolean specialConstructor;
+
+  /**
+   * This is the last token that has been consumed successfully.  If
+   * this object has been created due to a parse error, the token
+   * followng this token will (therefore) be the first error token.
+   */
+  public Token currentToken;
+
+  /**
+   * Each entry in this array is an array of integers.  Each array
+   * of integers represents a sequence of tokens (by their ordinal
+   * values) that is expected at this point of the parse.
+   */
+  public int[][] expectedTokenSequences;
+
+  /**
+   * This is a reference to the "tokenImage" array of the generated
+   * parser within which the parse error occurred.  This array is
+   * defined in the generated ...Constants interface.
+   */
+  public String[] tokenImage;
+
+  /**
+   * This method has the standard behavior when this object has been
+   * created using the standard constructors.  Otherwise, it uses
+   * "currentToken" and "expectedTokenSequences" to generate a parse
+   * error message and returns it.  If this object has been created
+   * due to a parse error, and you do not catch it (it gets thrown
+   * from the parser), then this method is called during the printing
+   * of the final stack trace, and hence the correct error message
+   * gets displayed.
+   */
+  public String getMessage() {
+    if (!specialConstructor) {
+      return super.getMessage();
+    }
+    StringBuffer expected = new StringBuffer();
+    int maxSize = 0;
+    for (int i = 0; i < expectedTokenSequences.length; i++) {
+      if (maxSize < expectedTokenSequences[i].length) {
+        maxSize = expectedTokenSequences[i].length;
+      }
+      for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+        expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" ");
+      }
+      if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+        expected.append("...");
+      }
+      expected.append(eol).append("    ");
+    }
+    String retval = "Encountered \"";
+    Token tok = currentToken.next;
+    for (int i = 0; i < maxSize; i++) {
+      if (i != 0) retval += " ";
+      if (tok.kind == 0) {
+        retval += tokenImage[0];
+        break;
+      }
+      retval += add_escapes(tok.image);
+      tok = tok.next; 
+    }
+    retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+    retval += "." + eol;
+    if (expectedTokenSequences.length == 1) {
+      retval += "Was expecting:" + eol + "    ";
+    } else {
+      retval += "Was expecting one of:" + eol + "    ";
+    }
+    retval += expected.toString();
+    return retval;
+  }
+
+  /**
+   * The end of line string for this machine.
+   */
+  protected String eol = System.getProperty("line.separator", "\n");
+ 
+  /**
+   * Used to convert raw characters to their escaped version
+   * when these raw version cannot be used as part of an ASCII
+   * string literal.
+   */
+  protected String add_escapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java
new file mode 100644
index 0000000..c9ba0b4
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/SimpleCharStream.java
@@ -0,0 +1,454 @@
+/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.address.parser;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).
+ */
+
+public class SimpleCharStream
+{
+  public static final boolean staticFlag = false;
+  int bufsize;
+  int available;
+  int tokenBegin;
+  public int bufpos = -1;
+  protected int bufline[];
+  protected int bufcolumn[];
+
+  protected int column = 0;
+  protected int line = 1;
+
+  protected boolean prevCharIsCR = false;
+  protected boolean prevCharIsLF = false;
+
+  protected java.io.Reader inputStream;
+
+  protected char[] buffer;
+  protected int maxNextCharInd = 0;
+  protected int inBuf = 0;
+  protected int tabSize = 8;
+
+  protected void setTabSize(int i) { tabSize = i; }
+  protected int getTabSize(int i) { return tabSize; }
+
+
+  protected void ExpandBuff(boolean wrapAround)
+  {
+     char[] newbuffer = new char[bufsize + 2048];
+     int newbufline[] = new int[bufsize + 2048];
+     int newbufcolumn[] = new int[bufsize + 2048];
+
+     try
+     {
+        if (wrapAround)
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           System.arraycopy(buffer, 0, newbuffer,
+                                             bufsize - tokenBegin, bufpos);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+        }
+        else
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos -= tokenBegin);
+        }
+     }
+     catch (Throwable t)
+     {
+        throw new Error(t.getMessage());
+     }
+
+
+     bufsize += 2048;
+     available = bufsize;
+     tokenBegin = 0;
+  }
+
+  protected void FillBuff() throws java.io.IOException
+  {
+     if (maxNextCharInd == available)
+     {
+        if (available == bufsize)
+        {
+           if (tokenBegin > 2048)
+           {
+              bufpos = maxNextCharInd = 0;
+              available = tokenBegin;
+           }
+           else if (tokenBegin < 0)
+              bufpos = maxNextCharInd = 0;
+           else
+              ExpandBuff(false);
+        }
+        else if (available > tokenBegin)
+           available = bufsize;
+        else if ((tokenBegin - available) < 2048)
+           ExpandBuff(true);
+        else
+           available = tokenBegin;
+     }
+
+     int i;
+     try {
+        if ((i = inputStream.read(buffer, maxNextCharInd,
+                                    available - maxNextCharInd)) == -1)
+        {
+           inputStream.close();
+           throw new java.io.IOException();
+        }
+        else
+           maxNextCharInd += i;
+        return;
+     }
+     catch(java.io.IOException e) {
+        --bufpos;
+        backup(0);
+        if (tokenBegin == -1)
+           tokenBegin = bufpos;
+        throw e;
+     }
+  }
+
+  public char BeginToken() throws java.io.IOException
+  {
+     tokenBegin = -1;
+     char c = readChar();
+     tokenBegin = bufpos;
+
+     return c;
+  }
+
+  protected void UpdateLineColumn(char c)
+  {
+     column++;
+
+     if (prevCharIsLF)
+     {
+        prevCharIsLF = false;
+        line += (column = 1);
+     }
+     else if (prevCharIsCR)
+     {
+        prevCharIsCR = false;
+        if (c == '\n')
+        {
+           prevCharIsLF = true;
+        }
+        else
+           line += (column = 1);
+     }
+
+     switch (c)
+     {
+        case '\r' :
+           prevCharIsCR = true;
+           break;
+        case '\n' :
+           prevCharIsLF = true;
+           break;
+        case '\t' :
+           column--;
+           column += (tabSize - (column % tabSize));
+           break;
+        default :
+           break;
+     }
+
+     bufline[bufpos] = line;
+     bufcolumn[bufpos] = column;
+  }
+
+  public char readChar() throws java.io.IOException
+  {
+     if (inBuf > 0)
+     {
+        --inBuf;
+
+        if (++bufpos == bufsize)
+           bufpos = 0;
+
+        return buffer[bufpos];
+     }
+
+     if (++bufpos >= maxNextCharInd)
+        FillBuff();
+
+     char c = buffer[bufpos];
+
+     UpdateLineColumn(c);
+     return (c);
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndColumn
+   */
+  @Deprecated
+  public int getColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndLine
+   */
+  @Deprecated
+  public int getLine() {
+     return bufline[bufpos];
+  }
+
+  public int getEndColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  public int getEndLine() {
+     return bufline[bufpos];
+  }
+
+  public int getBeginColumn() {
+     return bufcolumn[tokenBegin];
+  }
+
+  public int getBeginLine() {
+     return bufline[tokenBegin];
+  }
+
+  public void backup(int amount) {
+
+    inBuf += amount;
+    if ((bufpos -= amount) < 0)
+       bufpos += bufsize;
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    available = bufsize = buffersize;
+    buffer = new char[buffersize];
+    bufline = new int[buffersize];
+    bufcolumn = new int[buffersize];
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+                          int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.Reader dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    if (buffer == null || buffersize != buffer.length)
+    {
+      available = bufsize = buffersize;
+      buffer = new char[buffersize];
+      bufline = new int[buffersize];
+      bufcolumn = new int[buffersize];
+    }
+    prevCharIsLF = prevCharIsCR = false;
+    tokenBegin = inBuf = maxNextCharInd = 0;
+    bufpos = -1;
+  }
+
+  public void ReInit(java.io.Reader dstream, int startline,
+                     int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+
+  public void ReInit(java.io.Reader dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+  int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+     this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+     this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+     this(dstream, encoding, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+                          int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+     this(dstream, encoding, 1, 1, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  public void ReInit(java.io.InputStream dstream, int startline,
+                          int startcolumn, int buffersize)
+  {
+     ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(dstream, encoding, 1, 1, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                     int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(dstream, encoding, startline, startcolumn, 4096);
+  }
+  public void ReInit(java.io.InputStream dstream, int startline,
+                     int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+  public String GetImage()
+  {
+     if (bufpos >= tokenBegin)
+        return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+     else
+        return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+                              new String(buffer, 0, bufpos + 1);
+  }
+
+  public char[] GetSuffix(int len)
+  {
+     char[] ret = new char[len];
+
+     if ((bufpos + 1) >= len)
+        System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+     else
+     {
+        System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+                                                          len - bufpos - 1);
+        System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+     }
+
+     return ret;
+  }
+
+  public void Done()
+  {
+     buffer = null;
+     bufline = null;
+     bufcolumn = null;
+  }
+
+  /**
+   * Method to adjust line and column numbers for the start of a token.
+   */
+  public void adjustBeginLineColumn(int newLine, int newCol)
+  {
+     int start = tokenBegin;
+     int len;
+
+     if (bufpos >= tokenBegin)
+     {
+        len = bufpos - tokenBegin + inBuf + 1;
+     }
+     else
+     {
+        len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+     }
+
+     int i = 0, j = 0, k = 0;
+     int nextColDiff = 0, columnDiff = 0;
+
+     while (i < len &&
+            bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+     {
+        bufline[j] = newLine;
+        nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+        bufcolumn[j] = newCol + columnDiff;
+        columnDiff = nextColDiff;
+        i++;
+     } 
+
+     if (i < len)
+     {
+        bufline[j] = newLine++;
+        bufcolumn[j] = newCol + columnDiff;
+
+        while (i++ < len)
+        {
+           if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+              bufline[j] = newLine++;
+           else
+              bufline[j] = newLine;
+        }
+     }
+
+     line = bufline[j];
+     column = bufcolumn[j];
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/SimpleNode.java b/src/org/apache/james/mime4j/field/address/parser/SimpleNode.java
new file mode 100644
index 0000000..9bf537e
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/SimpleNode.java
@@ -0,0 +1,87 @@
+/* Generated By:JJTree: Do not edit this line. SimpleNode.java */
+
+package org.apache.james.mime4j.field.address.parser;
+
+public class SimpleNode extends org.apache.james.mime4j.field.address.parser.BaseNode implements Node {
+  protected Node parent;
+  protected Node[] children;
+  protected int id;
+  protected AddressListParser parser;
+
+  public SimpleNode(int i) {
+    id = i;
+  }
+
+  public SimpleNode(AddressListParser p, int i) {
+    this(i);
+    parser = p;
+  }
+
+  public void jjtOpen() {
+  }
+
+  public void jjtClose() {
+  }
+  
+  public void jjtSetParent(Node n) { parent = n; }
+  public Node jjtGetParent() { return parent; }
+
+  public void jjtAddChild(Node n, int i) {
+    if (children == null) {
+      children = new Node[i + 1];
+    } else if (i >= children.length) {
+      Node c[] = new Node[i + 1];
+      System.arraycopy(children, 0, c, 0, children.length);
+      children = c;
+    }
+    children[i] = n;
+  }
+
+  public Node jjtGetChild(int i) {
+    return children[i];
+  }
+
+  public int jjtGetNumChildren() {
+    return (children == null) ? 0 : children.length;
+  }
+
+  /** Accept the visitor. **/
+  public Object jjtAccept(AddressListParserVisitor visitor, Object data) {
+    return visitor.visit(this, data);
+  }
+
+  /** Accept the visitor. **/
+  public Object childrenAccept(AddressListParserVisitor visitor, Object data) {
+    if (children != null) {
+      for (int i = 0; i < children.length; ++i) {
+        children[i].jjtAccept(visitor, data);
+      }
+    }
+    return data;
+  }
+
+  /* You can override these two methods in subclasses of SimpleNode to
+     customize the way the node appears when the tree is dumped.  If
+     your output uses more than one line you should override
+     toString(String), otherwise overriding toString() is probably all
+     you need to do. */
+
+  public String toString() { return AddressListParserTreeConstants.jjtNodeName[id]; }
+  public String toString(String prefix) { return prefix + toString(); }
+
+  /* Override this method if you want to customize how the node dumps
+     out its children. */
+
+  public void dump(String prefix) {
+    System.out.println(toString(prefix));
+    if (children != null) {
+      for (int i = 0; i < children.length; ++i) {
+	SimpleNode n = (SimpleNode)children[i];
+	if (n != null) {
+	  n.dump(prefix + " ");
+	}
+      }
+    }
+  }
+}
+
diff --git a/src/org/apache/james/mime4j/field/address/parser/Token.java b/src/org/apache/james/mime4j/field/address/parser/Token.java
new file mode 100644
index 0000000..2382e8e
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/Token.java
@@ -0,0 +1,96 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.address.parser;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token {
+
+  /**
+   * An integer that describes the kind of this token.  This numbering
+   * system is determined by JavaCCParser, and a table of these numbers is
+   * stored in the file ...Constants.java.
+   */
+  public int kind;
+
+  /**
+   * beginLine and beginColumn describe the position of the first character
+   * of this token; endLine and endColumn describe the position of the
+   * last character of this token.
+   */
+  public int beginLine, beginColumn, endLine, endColumn;
+
+  /**
+   * The string image of the token.
+   */
+  public String image;
+
+  /**
+   * A reference to the next regular (non-special) token from the input
+   * stream.  If this is the last token from the input stream, or if the
+   * token manager has not read tokens beyond this one, this field is
+   * set to null.  This is true only if this token is also a regular
+   * token.  Otherwise, see below for a description of the contents of
+   * this field.
+   */
+  public Token next;
+
+  /**
+   * This field is used to access special tokens that occur prior to this
+   * token, but after the immediately preceding regular (non-special) token.
+   * If there are no such special tokens, this field is set to null.
+   * When there are more than one such special token, this field refers
+   * to the last of these special tokens, which in turn refers to the next
+   * previous special token through its specialToken field, and so on
+   * until the first special token (whose specialToken field is null).
+   * The next fields of special tokens refer to other special tokens that
+   * immediately follow it (without an intervening regular token).  If there
+   * is no such token, this field is null.
+   */
+  public Token specialToken;
+
+  /**
+   * Returns the image.
+   */
+  public String toString()
+  {
+     return image;
+  }
+
+  /**
+   * Returns a new Token object, by default. However, if you want, you
+   * can create and return subclass objects based on the value of ofKind.
+   * Simply add the cases to the switch for all those special cases.
+   * For example, if you have a subclass of Token called IDToken that
+   * you want to create if ofKind is ID, simlpy add something like :
+   *
+   *    case MyParserConstants.ID : return new IDToken();
+   *
+   * to the following switch statement. Then you can cast matchedToken
+   * variable to the appropriate type and use it in your lexical actions.
+   */
+  public static final Token newToken(int ofKind)
+  {
+     switch(ofKind)
+     {
+       default : return new Token();
+     }
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/address/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/address/parser/TokenMgrError.java
new file mode 100644
index 0000000..0299c85
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/address/parser/TokenMgrError.java
@@ -0,0 +1,148 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.address.parser;
+
+public class TokenMgrError extends Error
+{
+   /*
+    * Ordinals for various reasons why an Error of this type can be thrown.
+    */
+
+   /**
+    * Lexical error occured.
+    */
+   static final int LEXICAL_ERROR = 0;
+
+   /**
+    * An attempt wass made to create a second instance of a static token manager.
+    */
+   static final int STATIC_LEXER_ERROR = 1;
+
+   /**
+    * Tried to change to an invalid lexical state.
+    */
+   static final int INVALID_LEXICAL_STATE = 2;
+
+   /**
+    * Detected (and bailed out of) an infinite loop in the token manager.
+    */
+   static final int LOOP_DETECTED = 3;
+
+   /**
+    * Indicates the reason why the exception is thrown. It will have
+    * one of the above 4 values.
+    */
+   int errorCode;
+
+   /**
+    * Replaces unprintable characters by their espaced (or unicode escaped)
+    * equivalents in the given string
+    */
+   protected static final String addEscapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+   /**
+    * Returns a detailed message for the Error when it is thrown by the
+    * token manager to indicate a lexical error.
+    * Parameters : 
+    *    EOFSeen     : indicates if EOF caused the lexicl error
+    *    curLexState : lexical state in which this error occured
+    *    errorLine   : line number when the error occured
+    *    errorColumn : column number when the error occured
+    *    errorAfter  : prefix that was seen before this error occured
+    *    curchar     : the offending character
+    * Note: You can customize the lexical error message by modifying this method.
+    */
+   protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+      return("Lexical error at line " +
+           errorLine + ", column " +
+           errorColumn + ".  Encountered: " +
+           (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+           "after : \"" + addEscapes(errorAfter) + "\"");
+   }
+
+   /**
+    * You can also modify the body of this method to customize your error messages.
+    * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+    * of end-users concern, so you can return something like : 
+    *
+    *     "Internal Error : Please file a bug report .... "
+    *
+    * from this method for such cases in the release version of your parser.
+    */
+   public String getMessage() {
+      return super.getMessage();
+   }
+
+   /*
+    * Constructors of various flavors follow.
+    */
+
+   public TokenMgrError() {
+   }
+
+   public TokenMgrError(String message, int reason) {
+      super(message);
+      errorCode = reason;
+   }
+
+   public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+      this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+   }
+}
diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java
new file mode 100644
index 0000000..cacf3af
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParser.java
@@ -0,0 +1,268 @@
+/* Generated By:JavaCC: Do not edit this line. ContentTypeParser.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.contenttype.parser;
+
+import java.util.ArrayList;
+import java.util.Vector;
+
+public class ContentTypeParser implements ContentTypeParserConstants {
+
+        private String type;
+        private String subtype;
+        private ArrayList<String> paramNames = new ArrayList<String>();
+        private ArrayList<String> paramValues = new ArrayList<String>();
+
+        public String getType() { return type; }
+        public String getSubType() { return subtype; }
+        public ArrayList<String> getParamNames() { return paramNames; }
+        public ArrayList<String> getParamValues() { return paramValues; }
+
+    public static void main(String args[]) throws ParseException {
+        while (true) {
+            try {
+                ContentTypeParser parser = new ContentTypeParser(System.in);
+                parser.parseLine();
+            } catch (Exception x) {
+                x.printStackTrace();
+                return;
+            }
+        }
+    }
+
+  final public void parseLine() throws ParseException {
+    parse();
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case 1:
+      jj_consume_token(1);
+      break;
+    default:
+      jj_la1[0] = jj_gen;
+      ;
+    }
+    jj_consume_token(2);
+  }
+
+  final public void parseAll() throws ParseException {
+    parse();
+    jj_consume_token(0);
+  }
+
+  final public void parse() throws ParseException {
+        Token type;
+        Token subtype;
+    type = jj_consume_token(ATOKEN);
+    jj_consume_token(3);
+    subtype = jj_consume_token(ATOKEN);
+                this.type = type.image;
+                this.subtype = subtype.image;
+    label_1:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case 4:
+        ;
+        break;
+      default:
+        jj_la1[1] = jj_gen;
+        break label_1;
+      }
+      jj_consume_token(4);
+      parameter();
+    }
+  }
+
+  final public void parameter() throws ParseException {
+        Token attrib;
+        String val;
+    attrib = jj_consume_token(ATOKEN);
+    jj_consume_token(5);
+    val = value();
+                paramNames.add(attrib.image);
+                paramValues.add(val);
+  }
+
+  final public String value() throws ParseException {
+ Token t;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case ATOKEN:
+      t = jj_consume_token(ATOKEN);
+      break;
+    case QUOTEDSTRING:
+      t = jj_consume_token(QUOTEDSTRING);
+      break;
+    default:
+      jj_la1[2] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+          {if (true) return t.image;}
+    throw new Error("Missing return statement in function");
+  }
+
+  public ContentTypeParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  public Token token, jj_nt;
+  private int jj_ntk;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[3];
+  static private int[] jj_la1_0;
+  static {
+      jj_la1_0();
+   }
+   private static void jj_la1_0() {
+      jj_la1_0 = new int[] {0x2,0x10,0x280000,};
+   }
+
+  public ContentTypeParser(java.io.InputStream stream) {
+     this(stream, null);
+  }
+  public ContentTypeParser(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source = new ContentTypeParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 3; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(java.io.InputStream stream) {
+     ReInit(stream, null);
+  }
+  public void ReInit(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 3; i++) jj_la1[i] = -1;
+  }
+
+  public ContentTypeParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new ContentTypeParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 3; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 3; i++) jj_la1[i] = -1;
+  }
+
+  public ContentTypeParser(ContentTypeParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 3; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(ContentTypeParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 3; i++) jj_la1[i] = -1;
+  }
+
+  final private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+  final public Token getToken(int index) {
+    Token t = token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  final private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private Vector<int[]> jj_expentries = new Vector<int[]>();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+
+  public ParseException generateParseException() {
+    jj_expentries.removeAllElements();
+    boolean[] la1tokens = new boolean[24];
+    for (int i = 0; i < 24; i++) {
+      la1tokens[i] = false;
+    }
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 3; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 24; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.addElement(jj_expentry);
+      }
+    }
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = jj_expentries.elementAt(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  final public void enable_tracing() {
+  }
+
+  final public void disable_tracing() {
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java
new file mode 100644
index 0000000..d933d80
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserConstants.java
@@ -0,0 +1,62 @@
+/* Generated By:JavaCC: Do not edit this line. ContentTypeParserConstants.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.contenttype.parser;
+
+public interface ContentTypeParserConstants {
+
+  int EOF = 0;
+  int WS = 6;
+  int COMMENT = 8;
+  int QUOTEDSTRING = 19;
+  int DIGITS = 20;
+  int ATOKEN = 21;
+  int QUOTEDPAIR = 22;
+  int ANY = 23;
+
+  int DEFAULT = 0;
+  int INCOMMENT = 1;
+  int NESTED_COMMENT = 2;
+  int INQUOTEDSTRING = 3;
+
+  String[] tokenImage = {
+    "<EOF>",
+    "\"\\r\"",
+    "\"\\n\"",
+    "\"/\"",
+    "\";\"",
+    "\"=\"",
+    "<WS>",
+    "\"(\"",
+    "\")\"",
+    "<token of kind 9>",
+    "\"(\"",
+    "<token of kind 11>",
+    "<token of kind 12>",
+    "\"(\"",
+    "\")\"",
+    "<token of kind 15>",
+    "\"\\\"\"",
+    "<token of kind 17>",
+    "<token of kind 18>",
+    "\"\\\"\"",
+    "<DIGITS>",
+    "<ATOKEN>",
+    "<QUOTEDPAIR>",
+    "<ANY>",
+  };
+
+}
diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java
new file mode 100644
index 0000000..25b7aba
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/contenttype/parser/ContentTypeParserTokenManager.java
@@ -0,0 +1,877 @@
+/* Generated By:JavaCC: Do not edit this line. ContentTypeParserTokenManager.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.contenttype.parser;
+import java.util.ArrayList;
+
+public class ContentTypeParserTokenManager implements ContentTypeParserConstants
+{
+        // Keeps track of how many levels of comment nesting
+        // we've encountered.  This is only used when the 2nd
+        // level is reached, for example ((this)), not (this).
+        // This is because the outermost level must be treated
+        // specially anyway, because the outermost ")" has a
+        // different token type than inner ")" instances.
+        static int commentNest;
+  public  java.io.PrintStream debugStream = System.out;
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private final int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private final int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 10:
+         return jjStartNfaWithStates_0(0, 2, 2);
+      case 13:
+         return jjStartNfaWithStates_0(0, 1, 2);
+      case 34:
+         return jjStopAtPos(0, 16);
+      case 40:
+         return jjStopAtPos(0, 7);
+      case 47:
+         return jjStopAtPos(0, 3);
+      case 59:
+         return jjStopAtPos(0, 4);
+      case 61:
+         return jjStopAtPos(0, 5);
+      default :
+         return jjMoveNfa_0(3, 0);
+   }
+}
+private final void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private final void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private final void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+private final void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+private final void jjCheckNAddStates(int start)
+{
+   jjCheckNAdd(jjnextStates[start]);
+   jjCheckNAdd(jjnextStates[start + 1]);
+}
+static final long[] jjbitVec0 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private final int jjMoveNfa_0(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 3:
+                  if ((0x3ff6cfafffffdffL & l) != 0L)
+                  {
+                     if (kind > 21)
+                        kind = 21;
+                     jjCheckNAdd(2);
+                  }
+                  else if ((0x100000200L & l) != 0L)
+                  {
+                     if (kind > 6)
+                        kind = 6;
+                     jjCheckNAdd(0);
+                  }
+                  if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 20)
+                        kind = 20;
+                     jjCheckNAdd(1);
+                  }
+                  break;
+               case 0:
+                  if ((0x100000200L & l) == 0L)
+                     break;
+                  kind = 6;
+                  jjCheckNAdd(0);
+                  break;
+               case 1:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 20)
+                     kind = 20;
+                  jjCheckNAdd(1);
+                  break;
+               case 2:
+                  if ((0x3ff6cfafffffdffL & l) == 0L)
+                     break;
+                  if (kind > 21)
+                     kind = 21;
+                  jjCheckNAdd(2);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 3:
+               case 2:
+                  if ((0xffffffffc7fffffeL & l) == 0L)
+                     break;
+                  kind = 21;
+                  jjCheckNAdd(2);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 3:
+               case 2:
+                  if ((jjbitVec0[i2] & l2) == 0L)
+                     break;
+                  if (kind > 21)
+                     kind = 21;
+                  jjCheckNAdd(2);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_1(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_1(int pos, long active0)
+{
+   return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_1(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_1(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_1()
+{
+   switch(curChar)
+   {
+      case 40:
+         return jjStopAtPos(0, 10);
+      case 41:
+         return jjStopAtPos(0, 8);
+      default :
+         return jjMoveNfa_1(0, 0);
+   }
+}
+private final int jjMoveNfa_1(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 11)
+                     kind = 11;
+                  break;
+               case 1:
+                  if (kind > 9)
+                     kind = 9;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 11)
+                     kind = 11;
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 9)
+                     kind = 9;
+                  break;
+               case 2:
+                  if (kind > 11)
+                     kind = 11;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 11)
+                     kind = 11;
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 9)
+                     kind = 9;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_3(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_3(int pos, long active0)
+{
+   return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_3(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_3(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_3()
+{
+   switch(curChar)
+   {
+      case 34:
+         return jjStopAtPos(0, 19);
+      default :
+         return jjMoveNfa_3(0, 0);
+   }
+}
+private final int jjMoveNfa_3(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+               case 2:
+                  if ((0xfffffffbffffffffL & l) == 0L)
+                     break;
+                  if (kind > 18)
+                     kind = 18;
+                  jjCheckNAdd(2);
+                  break;
+               case 1:
+                  if (kind > 17)
+                     kind = 17;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                  {
+                     if (kind > 18)
+                        kind = 18;
+                     jjCheckNAdd(2);
+                  }
+                  else if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 17)
+                     kind = 17;
+                  break;
+               case 2:
+                  if ((0xffffffffefffffffL & l) == 0L)
+                     break;
+                  if (kind > 18)
+                     kind = 18;
+                  jjCheckNAdd(2);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+               case 2:
+                  if ((jjbitVec0[i2] & l2) == 0L)
+                     break;
+                  if (kind > 18)
+                     kind = 18;
+                  jjCheckNAdd(2);
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 17)
+                     kind = 17;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_2(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_2(int pos, long active0)
+{
+   return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_2(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_2(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_2()
+{
+   switch(curChar)
+   {
+      case 40:
+         return jjStopAtPos(0, 13);
+      case 41:
+         return jjStopAtPos(0, 14);
+      default :
+         return jjMoveNfa_2(0, 0);
+   }
+}
+private final int jjMoveNfa_2(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 15)
+                     kind = 15;
+                  break;
+               case 1:
+                  if (kind > 12)
+                     kind = 12;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 15)
+                     kind = 15;
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 12)
+                     kind = 12;
+                  break;
+               case 2:
+                  if (kind > 15)
+                     kind = 15;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 15)
+                     kind = 15;
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 12)
+                     kind = 12;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+};
+public static final String[] jjstrLiteralImages = {
+"", "\15", "\12", "\57", "\73", "\75", null, null, null, null, null, null, 
+null, null, null, null, null, null, null, null, null, null, null, null, };
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+   "INCOMMENT", 
+   "NESTED_COMMENT", 
+   "INQUOTEDSTRING", 
+};
+public static final int[] jjnewLexState = {
+   -1, -1, -1, -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, 3, -1, -1, 0, -1, -1, -1, -1, 
+};
+static final long[] jjtoToken = {
+   0x38003fL, 
+};
+static final long[] jjtoSkip = {
+   0x140L, 
+};
+static final long[] jjtoSpecial = {
+   0x40L, 
+};
+static final long[] jjtoMore = {
+   0x7fe80L, 
+};
+protected SimpleCharStream input_stream;
+private final int[] jjrounds = new int[3];
+private final int[] jjstateSet = new int[6];
+StringBuffer image;
+int jjimageLen;
+int lengthOfMatch;
+protected char curChar;
+public ContentTypeParserTokenManager(SimpleCharStream stream){
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+public ContentTypeParserTokenManager(SimpleCharStream stream, int lexState){
+   this(stream);
+   SwitchTo(lexState);
+}
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private final void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 3; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 4 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+   Token t = Token.newToken(jjmatchedKind);
+   t.kind = jjmatchedKind;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   t.image = (im == null) ? input_stream.GetImage() : im;
+   t.beginLine = input_stream.getBeginLine();
+   t.beginColumn = input_stream.getBeginColumn();
+   t.endLine = input_stream.getEndLine();
+   t.endColumn = input_stream.getEndColumn();
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+public Token getNextToken() 
+{
+  int kind;
+  Token specialToken = null;
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      matchedToken.specialToken = specialToken;
+      return matchedToken;
+   }
+   image = null;
+   jjimageLen = 0;
+
+   for (;;)
+   {
+     switch(curLexState)
+     {
+       case 0:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_0();
+         break;
+       case 1:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_1();
+         break;
+       case 2:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_2();
+         break;
+       case 3:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_3();
+         break;
+     }
+     if (jjmatchedKind != 0x7fffffff)
+     {
+        if (jjmatchedPos + 1 < curPos)
+           input_stream.backup(curPos - jjmatchedPos - 1);
+        if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+        {
+           matchedToken = jjFillToken();
+           matchedToken.specialToken = specialToken;
+           TokenLexicalActions(matchedToken);
+       if (jjnewLexState[jjmatchedKind] != -1)
+         curLexState = jjnewLexState[jjmatchedKind];
+           return matchedToken;
+        }
+        else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+        {
+           if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+           {
+              matchedToken = jjFillToken();
+              if (specialToken == null)
+                 specialToken = matchedToken;
+              else
+              {
+                 matchedToken.specialToken = specialToken;
+                 specialToken = (specialToken.next = matchedToken);
+              }
+           }
+         if (jjnewLexState[jjmatchedKind] != -1)
+           curLexState = jjnewLexState[jjmatchedKind];
+           continue EOFLoop;
+        }
+        MoreLexicalActions();
+      if (jjnewLexState[jjmatchedKind] != -1)
+        curLexState = jjnewLexState[jjmatchedKind];
+        curPos = 0;
+        jjmatchedKind = 0x7fffffff;
+        try {
+           curChar = input_stream.readChar();
+           continue;
+        }
+        catch (java.io.IOException e1) { }
+     }
+     int error_line = input_stream.getEndLine();
+     int error_column = input_stream.getEndColumn();
+     String error_after = null;
+     boolean EOFSeen = false;
+     try { input_stream.readChar(); input_stream.backup(1); }
+     catch (java.io.IOException e1) {
+        EOFSeen = true;
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+        if (curChar == '\n' || curChar == '\r') {
+           error_line++;
+           error_column = 0;
+        }
+        else
+           error_column++;
+     }
+     if (!EOFSeen) {
+        input_stream.backup(1);
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+     }
+     throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+   }
+  }
+}
+
+void MoreLexicalActions()
+{
+   jjimageLen += (lengthOfMatch = jjmatchedPos + 1);
+   switch(jjmatchedKind)
+   {
+      case 9 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      case 10 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              commentNest = 1;
+         break;
+      case 12 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      case 13 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              ++commentNest;
+         break;
+      case 14 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT);
+         break;
+      case 16 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+               image.deleteCharAt(image.length() - 1);
+         break;
+      case 17 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      default : 
+         break;
+   }
+}
+void TokenLexicalActions(Token matchedToken)
+{
+   switch(jjmatchedKind)
+   {
+      case 19 :
+        if (image == null)
+            image = new StringBuffer();
+            image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+                                 matchedToken.image = image.substring(0, image.length() - 1);
+         break;
+      default : 
+         break;
+   }
+}
+}
diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/ParseException.java b/src/org/apache/james/mime4j/field/contenttype/parser/ParseException.java
new file mode 100644
index 0000000..d9b69b2
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/contenttype/parser/ParseException.java
@@ -0,0 +1,207 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.contenttype.parser;
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+  /**
+   * This constructor is used by the method "generateParseException"
+   * in the generated parser.  Calling this constructor generates
+   * a new object of this type with the fields "currentToken",
+   * "expectedTokenSequences", and "tokenImage" set.  The boolean
+   * flag "specialConstructor" is also set to true to indicate that
+   * this constructor was used to create this object.
+   * This constructor calls its super class with the empty string
+   * to force the "toString" method of parent class "Throwable" to
+   * print the error message in the form:
+   *     ParseException: <result of getMessage>
+   */
+  public ParseException(Token currentTokenVal,
+                        int[][] expectedTokenSequencesVal,
+                        String[] tokenImageVal
+                       )
+  {
+    super("");
+    specialConstructor = true;
+    currentToken = currentTokenVal;
+    expectedTokenSequences = expectedTokenSequencesVal;
+    tokenImage = tokenImageVal;
+  }
+
+  /**
+   * The following constructors are for use by you for whatever
+   * purpose you can think of.  Constructing the exception in this
+   * manner makes the exception behave in the normal way - i.e., as
+   * documented in the class "Throwable".  The fields "errorToken",
+   * "expectedTokenSequences", and "tokenImage" do not contain
+   * relevant information.  The JavaCC generated code does not use
+   * these constructors.
+   */
+
+  public ParseException() {
+    super();
+    specialConstructor = false;
+  }
+
+  public ParseException(String message) {
+    super(message);
+    specialConstructor = false;
+  }
+
+  /**
+   * This variable determines which constructor was used to create
+   * this object and thereby affects the semantics of the
+   * "getMessage" method (see below).
+   */
+  protected boolean specialConstructor;
+
+  /**
+   * This is the last token that has been consumed successfully.  If
+   * this object has been created due to a parse error, the token
+   * followng this token will (therefore) be the first error token.
+   */
+  public Token currentToken;
+
+  /**
+   * Each entry in this array is an array of integers.  Each array
+   * of integers represents a sequence of tokens (by their ordinal
+   * values) that is expected at this point of the parse.
+   */
+  public int[][] expectedTokenSequences;
+
+  /**
+   * This is a reference to the "tokenImage" array of the generated
+   * parser within which the parse error occurred.  This array is
+   * defined in the generated ...Constants interface.
+   */
+  public String[] tokenImage;
+
+  /**
+   * This method has the standard behavior when this object has been
+   * created using the standard constructors.  Otherwise, it uses
+   * "currentToken" and "expectedTokenSequences" to generate a parse
+   * error message and returns it.  If this object has been created
+   * due to a parse error, and you do not catch it (it gets thrown
+   * from the parser), then this method is called during the printing
+   * of the final stack trace, and hence the correct error message
+   * gets displayed.
+   */
+  public String getMessage() {
+    if (!specialConstructor) {
+      return super.getMessage();
+    }
+    StringBuffer expected = new StringBuffer();
+    int maxSize = 0;
+    for (int i = 0; i < expectedTokenSequences.length; i++) {
+      if (maxSize < expectedTokenSequences[i].length) {
+        maxSize = expectedTokenSequences[i].length;
+      }
+      for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+        expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" ");
+      }
+      if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+        expected.append("...");
+      }
+      expected.append(eol).append("    ");
+    }
+    String retval = "Encountered \"";
+    Token tok = currentToken.next;
+    for (int i = 0; i < maxSize; i++) {
+      if (i != 0) retval += " ";
+      if (tok.kind == 0) {
+        retval += tokenImage[0];
+        break;
+      }
+      retval += add_escapes(tok.image);
+      tok = tok.next; 
+    }
+    retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+    retval += "." + eol;
+    if (expectedTokenSequences.length == 1) {
+      retval += "Was expecting:" + eol + "    ";
+    } else {
+      retval += "Was expecting one of:" + eol + "    ";
+    }
+    retval += expected.toString();
+    return retval;
+  }
+
+  /**
+   * The end of line string for this machine.
+   */
+  protected String eol = System.getProperty("line.separator", "\n");
+ 
+  /**
+   * Used to convert raw characters to their escaped version
+   * when these raw version cannot be used as part of an ASCII
+   * string literal.
+   */
+  protected String add_escapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+}
diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java
new file mode 100644
index 0000000..ae035b7
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/contenttype/parser/SimpleCharStream.java
@@ -0,0 +1,454 @@
+/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.contenttype.parser;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).
+ */
+
+public class SimpleCharStream
+{
+  public static final boolean staticFlag = false;
+  int bufsize;
+  int available;
+  int tokenBegin;
+  public int bufpos = -1;
+  protected int bufline[];
+  protected int bufcolumn[];
+
+  protected int column = 0;
+  protected int line = 1;
+
+  protected boolean prevCharIsCR = false;
+  protected boolean prevCharIsLF = false;
+
+  protected java.io.Reader inputStream;
+
+  protected char[] buffer;
+  protected int maxNextCharInd = 0;
+  protected int inBuf = 0;
+  protected int tabSize = 8;
+
+  protected void setTabSize(int i) { tabSize = i; }
+  protected int getTabSize(int i) { return tabSize; }
+
+
+  protected void ExpandBuff(boolean wrapAround)
+  {
+     char[] newbuffer = new char[bufsize + 2048];
+     int newbufline[] = new int[bufsize + 2048];
+     int newbufcolumn[] = new int[bufsize + 2048];
+
+     try
+     {
+        if (wrapAround)
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           System.arraycopy(buffer, 0, newbuffer,
+                                             bufsize - tokenBegin, bufpos);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+        }
+        else
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos -= tokenBegin);
+        }
+     }
+     catch (Throwable t)
+     {
+        throw new Error(t.getMessage());
+     }
+
+
+     bufsize += 2048;
+     available = bufsize;
+     tokenBegin = 0;
+  }
+
+  protected void FillBuff() throws java.io.IOException
+  {
+     if (maxNextCharInd == available)
+     {
+        if (available == bufsize)
+        {
+           if (tokenBegin > 2048)
+           {
+              bufpos = maxNextCharInd = 0;
+              available = tokenBegin;
+           }
+           else if (tokenBegin < 0)
+              bufpos = maxNextCharInd = 0;
+           else
+              ExpandBuff(false);
+        }
+        else if (available > tokenBegin)
+           available = bufsize;
+        else if ((tokenBegin - available) < 2048)
+           ExpandBuff(true);
+        else
+           available = tokenBegin;
+     }
+
+     int i;
+     try {
+        if ((i = inputStream.read(buffer, maxNextCharInd,
+                                    available - maxNextCharInd)) == -1)
+        {
+           inputStream.close();
+           throw new java.io.IOException();
+        }
+        else
+           maxNextCharInd += i;
+        return;
+     }
+     catch(java.io.IOException e) {
+        --bufpos;
+        backup(0);
+        if (tokenBegin == -1)
+           tokenBegin = bufpos;
+        throw e;
+     }
+  }
+
+  public char BeginToken() throws java.io.IOException
+  {
+     tokenBegin = -1;
+     char c = readChar();
+     tokenBegin = bufpos;
+
+     return c;
+  }
+
+  protected void UpdateLineColumn(char c)
+  {
+     column++;
+
+     if (prevCharIsLF)
+     {
+        prevCharIsLF = false;
+        line += (column = 1);
+     }
+     else if (prevCharIsCR)
+     {
+        prevCharIsCR = false;
+        if (c == '\n')
+        {
+           prevCharIsLF = true;
+        }
+        else
+           line += (column = 1);
+     }
+
+     switch (c)
+     {
+        case '\r' :
+           prevCharIsCR = true;
+           break;
+        case '\n' :
+           prevCharIsLF = true;
+           break;
+        case '\t' :
+           column--;
+           column += (tabSize - (column % tabSize));
+           break;
+        default :
+           break;
+     }
+
+     bufline[bufpos] = line;
+     bufcolumn[bufpos] = column;
+  }
+
+  public char readChar() throws java.io.IOException
+  {
+     if (inBuf > 0)
+     {
+        --inBuf;
+
+        if (++bufpos == bufsize)
+           bufpos = 0;
+
+        return buffer[bufpos];
+     }
+
+     if (++bufpos >= maxNextCharInd)
+        FillBuff();
+
+     char c = buffer[bufpos];
+
+     UpdateLineColumn(c);
+     return (c);
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndColumn
+   */
+  @Deprecated
+  public int getColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndLine
+   */
+  @Deprecated
+  public int getLine() {
+     return bufline[bufpos];
+  }
+
+  public int getEndColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  public int getEndLine() {
+     return bufline[bufpos];
+  }
+
+  public int getBeginColumn() {
+     return bufcolumn[tokenBegin];
+  }
+
+  public int getBeginLine() {
+     return bufline[tokenBegin];
+  }
+
+  public void backup(int amount) {
+
+    inBuf += amount;
+    if ((bufpos -= amount) < 0)
+       bufpos += bufsize;
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    available = bufsize = buffersize;
+    buffer = new char[buffersize];
+    bufline = new int[buffersize];
+    bufcolumn = new int[buffersize];
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+                          int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.Reader dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    if (buffer == null || buffersize != buffer.length)
+    {
+      available = bufsize = buffersize;
+      buffer = new char[buffersize];
+      bufline = new int[buffersize];
+      bufcolumn = new int[buffersize];
+    }
+    prevCharIsLF = prevCharIsCR = false;
+    tokenBegin = inBuf = maxNextCharInd = 0;
+    bufpos = -1;
+  }
+
+  public void ReInit(java.io.Reader dstream, int startline,
+                     int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+
+  public void ReInit(java.io.Reader dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+  int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+     this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+     this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+     this(dstream, encoding, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+                          int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+     this(dstream, encoding, 1, 1, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  public void ReInit(java.io.InputStream dstream, int startline,
+                          int startcolumn, int buffersize)
+  {
+     ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(dstream, encoding, 1, 1, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                     int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(dstream, encoding, startline, startcolumn, 4096);
+  }
+  public void ReInit(java.io.InputStream dstream, int startline,
+                     int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+  public String GetImage()
+  {
+     if (bufpos >= tokenBegin)
+        return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+     else
+        return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+                              new String(buffer, 0, bufpos + 1);
+  }
+
+  public char[] GetSuffix(int len)
+  {
+     char[] ret = new char[len];
+
+     if ((bufpos + 1) >= len)
+        System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+     else
+     {
+        System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+                                                          len - bufpos - 1);
+        System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+     }
+
+     return ret;
+  }
+
+  public void Done()
+  {
+     buffer = null;
+     bufline = null;
+     bufcolumn = null;
+  }
+
+  /**
+   * Method to adjust line and column numbers for the start of a token.
+   */
+  public void adjustBeginLineColumn(int newLine, int newCol)
+  {
+     int start = tokenBegin;
+     int len;
+
+     if (bufpos >= tokenBegin)
+     {
+        len = bufpos - tokenBegin + inBuf + 1;
+     }
+     else
+     {
+        len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+     }
+
+     int i = 0, j = 0, k = 0;
+     int nextColDiff = 0, columnDiff = 0;
+
+     while (i < len &&
+            bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+     {
+        bufline[j] = newLine;
+        nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+        bufcolumn[j] = newCol + columnDiff;
+        columnDiff = nextColDiff;
+        i++;
+     } 
+
+     if (i < len)
+     {
+        bufline[j] = newLine++;
+        bufcolumn[j] = newCol + columnDiff;
+
+        while (i++ < len)
+        {
+           if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+              bufline[j] = newLine++;
+           else
+              bufline[j] = newLine;
+        }
+     }
+
+     line = bufline[j];
+     column = bufcolumn[j];
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/Token.java b/src/org/apache/james/mime4j/field/contenttype/parser/Token.java
new file mode 100644
index 0000000..34e65ee
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/contenttype/parser/Token.java
@@ -0,0 +1,96 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.contenttype.parser;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token {
+
+  /**
+   * An integer that describes the kind of this token.  This numbering
+   * system is determined by JavaCCParser, and a table of these numbers is
+   * stored in the file ...Constants.java.
+   */
+  public int kind;
+
+  /**
+   * beginLine and beginColumn describe the position of the first character
+   * of this token; endLine and endColumn describe the position of the
+   * last character of this token.
+   */
+  public int beginLine, beginColumn, endLine, endColumn;
+
+  /**
+   * The string image of the token.
+   */
+  public String image;
+
+  /**
+   * A reference to the next regular (non-special) token from the input
+   * stream.  If this is the last token from the input stream, or if the
+   * token manager has not read tokens beyond this one, this field is
+   * set to null.  This is true only if this token is also a regular
+   * token.  Otherwise, see below for a description of the contents of
+   * this field.
+   */
+  public Token next;
+
+  /**
+   * This field is used to access special tokens that occur prior to this
+   * token, but after the immediately preceding regular (non-special) token.
+   * If there are no such special tokens, this field is set to null.
+   * When there are more than one such special token, this field refers
+   * to the last of these special tokens, which in turn refers to the next
+   * previous special token through its specialToken field, and so on
+   * until the first special token (whose specialToken field is null).
+   * The next fields of special tokens refer to other special tokens that
+   * immediately follow it (without an intervening regular token).  If there
+   * is no such token, this field is null.
+   */
+  public Token specialToken;
+
+  /**
+   * Returns the image.
+   */
+  public String toString()
+  {
+     return image;
+  }
+
+  /**
+   * Returns a new Token object, by default. However, if you want, you
+   * can create and return subclass objects based on the value of ofKind.
+   * Simply add the cases to the switch for all those special cases.
+   * For example, if you have a subclass of Token called IDToken that
+   * you want to create if ofKind is ID, simlpy add something like :
+   *
+   *    case MyParserConstants.ID : return new IDToken();
+   *
+   * to the following switch statement. Then you can cast matchedToken
+   * variable to the appropriate type and use it in your lexical actions.
+   */
+  public static final Token newToken(int ofKind)
+  {
+     switch(ofKind)
+     {
+       default : return new Token();
+     }
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java
new file mode 100644
index 0000000..ea5a782
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/contenttype/parser/TokenMgrError.java
@@ -0,0 +1,148 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.contenttype.parser;
+
+public class TokenMgrError extends Error
+{
+   /*
+    * Ordinals for various reasons why an Error of this type can be thrown.
+    */
+
+   /**
+    * Lexical error occured.
+    */
+   static final int LEXICAL_ERROR = 0;
+
+   /**
+    * An attempt wass made to create a second instance of a static token manager.
+    */
+   static final int STATIC_LEXER_ERROR = 1;
+
+   /**
+    * Tried to change to an invalid lexical state.
+    */
+   static final int INVALID_LEXICAL_STATE = 2;
+
+   /**
+    * Detected (and bailed out of) an infinite loop in the token manager.
+    */
+   static final int LOOP_DETECTED = 3;
+
+   /**
+    * Indicates the reason why the exception is thrown. It will have
+    * one of the above 4 values.
+    */
+   int errorCode;
+
+   /**
+    * Replaces unprintable characters by their espaced (or unicode escaped)
+    * equivalents in the given string
+    */
+   protected static final String addEscapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+   /**
+    * Returns a detailed message for the Error when it is thrown by the
+    * token manager to indicate a lexical error.
+    * Parameters : 
+    *    EOFSeen     : indicates if EOF caused the lexicl error
+    *    curLexState : lexical state in which this error occured
+    *    errorLine   : line number when the error occured
+    *    errorColumn : column number when the error occured
+    *    errorAfter  : prefix that was seen before this error occured
+    *    curchar     : the offending character
+    * Note: You can customize the lexical error message by modifying this method.
+    */
+   protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+      return("Lexical error at line " +
+           errorLine + ", column " +
+           errorColumn + ".  Encountered: " +
+           (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+           "after : \"" + addEscapes(errorAfter) + "\"");
+   }
+
+   /**
+    * You can also modify the body of this method to customize your error messages.
+    * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+    * of end-users concern, so you can return something like : 
+    *
+    *     "Internal Error : Please file a bug report .... "
+    *
+    * from this method for such cases in the release version of your parser.
+    */
+   public String getMessage() {
+      return super.getMessage();
+   }
+
+   /*
+    * Constructors of various flavors follow.
+    */
+
+   public TokenMgrError() {
+   }
+
+   public TokenMgrError(String message, int reason) {
+      super(message);
+      errorCode = reason;
+   }
+
+   public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+      this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+   }
+}
diff --git a/src/org/apache/james/mime4j/field/datetime/DateTime.java b/src/org/apache/james/mime4j/field/datetime/DateTime.java
new file mode 100644
index 0000000..506ff54
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/datetime/DateTime.java
@@ -0,0 +1,127 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.field.datetime;
+
+import org.apache.james.mime4j.field.datetime.parser.DateTimeParser;
+import org.apache.james.mime4j.field.datetime.parser.ParseException;
+import org.apache.james.mime4j.field.datetime.parser.TokenMgrError;
+
+import java.util.Date;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.GregorianCalendar;
+import java.io.StringReader;
+
+public class DateTime {
+    private final Date date;
+    private final int year;
+    private final int month;
+    private final int day;
+    private final int hour;
+    private final int minute;
+    private final int second;
+    private final int timeZone;
+
+    public DateTime(String yearString, int month, int day, int hour, int minute, int second, int timeZone) {
+        this.year = convertToYear(yearString);
+        this.date = convertToDate(year, month, day, hour, minute, second, timeZone);
+        this.month = month;
+        this.day = day;
+        this.hour = hour;
+        this.minute = minute;
+        this.second = second;
+        this.timeZone = timeZone;
+    }
+
+    private int convertToYear(String yearString) {
+        int year = Integer.parseInt(yearString);
+        switch (yearString.length()) {
+            case 1:
+            case 2:
+                if (year >= 0 && year < 50)
+                    return 2000 + year;
+                else
+                    return 1900 + year;
+            case 3:
+                return 1900 + year;
+            default:
+                return year;
+        }
+    }
+
+    public static Date convertToDate(int year, int month, int day, int hour, int minute, int second, int timeZone) {
+        Calendar c = new GregorianCalendar(TimeZone.getTimeZone("GMT+0"));
+        c.set(year, month - 1, day, hour, minute, second);
+        c.set(Calendar.MILLISECOND, 0);
+
+        if (timeZone != Integer.MIN_VALUE) {
+            int minutes = ((timeZone / 100) * 60) + timeZone % 100;
+            c.add(Calendar.MINUTE, -1 * minutes);
+        }
+
+        return c.getTime();
+    }
+
+    public Date getDate() {
+        return date;
+    }
+
+    public int getYear() {
+        return year;
+    }
+
+    public int getMonth() {
+        return month;
+    }
+
+    public int getDay() {
+        return day;
+    }
+
+    public int getHour() {
+        return hour;
+    }
+
+    public int getMinute() {
+        return minute;
+    }
+
+    public int getSecond() {
+        return second;
+    }
+
+    public int getTimeZone() {
+        return timeZone;
+    }
+
+    public void print() {
+        System.out.println(getYear() + " " + getMonth() + " " + getDay() + "; " + getHour() + " " + getMinute() + " " + getSecond() + " " + getTimeZone());
+    }
+
+
+    public static DateTime parse(String dateString) throws ParseException {
+        try {
+            return new DateTimeParser(new StringReader(dateString)).parseAll();
+        }
+        catch (TokenMgrError err) {
+            throw new ParseException(err.getMessage());
+        }
+    }
+}
diff --git a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java
new file mode 100644
index 0000000..43edebb
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParser.java
@@ -0,0 +1,570 @@
+/* Generated By:JavaCC: Do not edit this line. DateTimeParser.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.datetime.parser;
+
+import org.apache.james.mime4j.field.datetime.DateTime;
+
+import java.util.Vector;
+
+public class DateTimeParser implements DateTimeParserConstants {
+    private static final boolean ignoreMilitaryZoneOffset = true;
+
+    public static void main(String args[]) throws ParseException {
+                while (true) {
+                    try {
+                                DateTimeParser parser = new DateTimeParser(System.in);
+                        parser.parseLine();
+                    } catch (Exception x) {
+                                x.printStackTrace();
+                                return;
+                    }
+                }
+    }
+
+    private static int parseDigits(Token token) {
+        return Integer.parseInt(token.image, 10);
+    }
+
+    private static int getMilitaryZoneOffset(char c) {
+        if (ignoreMilitaryZoneOffset)
+            return 0;
+
+        c = Character.toUpperCase(c);
+
+        switch (c) {
+            case 'A': return 1;
+            case 'B': return 2;
+            case 'C': return 3;
+            case 'D': return 4;
+            case 'E': return 5;
+            case 'F': return 6;
+            case 'G': return 7;
+            case 'H': return 8;
+            case 'I': return 9;
+            case 'K': return 10;
+            case 'L': return 11;
+            case 'M': return 12;
+
+            case 'N': return -1;
+            case 'O': return -2;
+            case 'P': return -3;
+            case 'Q': return -4;
+            case 'R': return -5;
+            case 'S': return -6;
+            case 'T': return -7;
+            case 'U': return -8;
+            case 'V': return -9;
+            case 'W': return -10;
+            case 'X': return -11;
+            case 'Y': return -12;
+
+            case 'Z': return 0;
+            default: return 0;
+        }
+    }
+
+    private static class Time {
+        private int hour;
+        private int minute;
+        private int second;
+        private int zone;
+
+        public Time(int hour, int minute, int second, int zone) {
+            this.hour = hour;
+            this.minute = minute;
+            this.second = second;
+            this.zone = zone;
+        }
+
+        public int getHour() { return hour; }
+        public int getMinute() { return minute; }
+        public int getSecond() { return second; }
+        public int getZone() { return zone; }
+    }
+
+    private static class Date {
+        private String year;
+        private int month;
+        private int day;
+
+        public Date(String year, int month, int day) {
+            this.year = year;
+            this.month = month;
+            this.day = day;
+        }
+
+        public String getYear() { return year; }
+        public int getMonth() { return month; }
+        public int getDay() { return day; }
+    }
+
+  final public DateTime parseLine() throws ParseException {
+ DateTime dt;
+    dt = date_time();
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case 1:
+      jj_consume_token(1);
+      break;
+    default:
+      jj_la1[0] = jj_gen;
+      ;
+    }
+    jj_consume_token(2);
+          {if (true) return dt;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public DateTime parseAll() throws ParseException {
+ DateTime dt;
+    dt = date_time();
+    jj_consume_token(0);
+          {if (true) return dt;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public DateTime date_time() throws ParseException {
+ Date d; Time t;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case 4:
+    case 5:
+    case 6:
+    case 7:
+    case 8:
+    case 9:
+    case 10:
+      day_of_week();
+      jj_consume_token(3);
+      break;
+    default:
+      jj_la1[1] = jj_gen;
+      ;
+    }
+    d = date();
+    t = time();
+            {if (true) return new DateTime(
+                    d.getYear(),
+                    d.getMonth(),
+                    d.getDay(),
+                    t.getHour(),
+                    t.getMinute(),
+                    t.getSecond(),
+                    t.getZone());}    // time zone offset
+
+    throw new Error("Missing return statement in function");
+  }
+
+  final public String day_of_week() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case 4:
+      jj_consume_token(4);
+      break;
+    case 5:
+      jj_consume_token(5);
+      break;
+    case 6:
+      jj_consume_token(6);
+      break;
+    case 7:
+      jj_consume_token(7);
+      break;
+    case 8:
+      jj_consume_token(8);
+      break;
+    case 9:
+      jj_consume_token(9);
+      break;
+    case 10:
+      jj_consume_token(10);
+      break;
+    default:
+      jj_la1[2] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+      {if (true) return token.image;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Date date() throws ParseException {
+ int d, m; String y;
+    d = day();
+    m = month();
+    y = year();
+      {if (true) return new Date(y, m, d);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public int day() throws ParseException {
+ Token t;
+    t = jj_consume_token(DIGITS);
+                 {if (true) return parseDigits(t);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public int month() throws ParseException {
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case 11:
+      jj_consume_token(11);
+            {if (true) return 1;}
+      break;
+    case 12:
+      jj_consume_token(12);
+            {if (true) return 2;}
+      break;
+    case 13:
+      jj_consume_token(13);
+            {if (true) return 3;}
+      break;
+    case 14:
+      jj_consume_token(14);
+            {if (true) return 4;}
+      break;
+    case 15:
+      jj_consume_token(15);
+            {if (true) return 5;}
+      break;
+    case 16:
+      jj_consume_token(16);
+            {if (true) return 6;}
+      break;
+    case 17:
+      jj_consume_token(17);
+            {if (true) return 7;}
+      break;
+    case 18:
+      jj_consume_token(18);
+            {if (true) return 8;}
+      break;
+    case 19:
+      jj_consume_token(19);
+            {if (true) return 9;}
+      break;
+    case 20:
+      jj_consume_token(20);
+            {if (true) return 10;}
+      break;
+    case 21:
+      jj_consume_token(21);
+            {if (true) return 11;}
+      break;
+    case 22:
+      jj_consume_token(22);
+            {if (true) return 12;}
+      break;
+    default:
+      jj_la1[3] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  final public String year() throws ParseException {
+ Token t;
+    t = jj_consume_token(DIGITS);
+                 {if (true) return t.image;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Time time() throws ParseException {
+ int h, m, s=0, z;
+    h = hour();
+    jj_consume_token(23);
+    m = minute();
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case 23:
+      jj_consume_token(23);
+      s = second();
+      break;
+    default:
+      jj_la1[4] = jj_gen;
+      ;
+    }
+    z = zone();
+      {if (true) return new Time(h, m, s, z);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public int hour() throws ParseException {
+ Token t;
+    t = jj_consume_token(DIGITS);
+                 {if (true) return parseDigits(t);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public int minute() throws ParseException {
+ Token t;
+    t = jj_consume_token(DIGITS);
+                 {if (true) return parseDigits(t);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public int second() throws ParseException {
+ Token t;
+    t = jj_consume_token(DIGITS);
+                 {if (true) return parseDigits(t);}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public int zone() throws ParseException {
+  Token t, u; int z;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case OFFSETDIR:
+      t = jj_consume_token(OFFSETDIR);
+      u = jj_consume_token(DIGITS);
+                                              z=parseDigits(u)*(t.image.equals("-") ? -1 : 1);
+      break;
+    case 25:
+    case 26:
+    case 27:
+    case 28:
+    case 29:
+    case 30:
+    case 31:
+    case 32:
+    case 33:
+    case 34:
+    case MILITARY_ZONE:
+      z = obs_zone();
+      break;
+    default:
+      jj_la1[5] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+      {if (true) return z;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public int obs_zone() throws ParseException {
+ Token t; int z;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case 25:
+      jj_consume_token(25);
+            z=0;
+      break;
+    case 26:
+      jj_consume_token(26);
+            z=0;
+      break;
+    case 27:
+      jj_consume_token(27);
+            z=-5;
+      break;
+    case 28:
+      jj_consume_token(28);
+            z=-4;
+      break;
+    case 29:
+      jj_consume_token(29);
+            z=-6;
+      break;
+    case 30:
+      jj_consume_token(30);
+            z=-5;
+      break;
+    case 31:
+      jj_consume_token(31);
+            z=-7;
+      break;
+    case 32:
+      jj_consume_token(32);
+            z=-6;
+      break;
+    case 33:
+      jj_consume_token(33);
+            z=-8;
+      break;
+    case 34:
+      jj_consume_token(34);
+            z=-7;
+      break;
+    case MILITARY_ZONE:
+      t = jj_consume_token(MILITARY_ZONE);
+                                                             z=getMilitaryZoneOffset(t.image.charAt(0));
+      break;
+    default:
+      jj_la1[6] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+      {if (true) return z * 100;}
+    throw new Error("Missing return statement in function");
+  }
+
+  public DateTimeParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  public Token token, jj_nt;
+  private int jj_ntk;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[7];
+  static private int[] jj_la1_0;
+  static private int[] jj_la1_1;
+  static {
+      jj_la1_0();
+      jj_la1_1();
+   }
+   private static void jj_la1_0() {
+      jj_la1_0 = new int[] {0x2,0x7f0,0x7f0,0x7ff800,0x800000,0xff000000,0xfe000000,};
+   }
+   private static void jj_la1_1() {
+      jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0xf,0xf,};
+   }
+
+  public DateTimeParser(java.io.InputStream stream) {
+     this(stream, null);
+  }
+  public DateTimeParser(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source = new DateTimeParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 7; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(java.io.InputStream stream) {
+     ReInit(stream, null);
+  }
+  public void ReInit(java.io.InputStream stream, String encoding) {
+    try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 7; i++) jj_la1[i] = -1;
+  }
+
+  public DateTimeParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new DateTimeParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 7; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 7; i++) jj_la1[i] = -1;
+  }
+
+  public DateTimeParser(DateTimeParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 7; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(DateTimeParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 7; i++) jj_la1[i] = -1;
+  }
+
+  final private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+  final public Token getToken(int index) {
+    Token t = token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  final private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private Vector<int[]> jj_expentries = new Vector<int[]>();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+
+  public ParseException generateParseException() {
+    jj_expentries.removeAllElements();
+    boolean[] la1tokens = new boolean[49];
+    for (int i = 0; i < 49; i++) {
+      la1tokens[i] = false;
+    }
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 7; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+          if ((jj_la1_1[i] & (1<<j)) != 0) {
+            la1tokens[32+j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 49; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.addElement(jj_expentry);
+      }
+    }
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = jj_expentries.elementAt(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  final public void enable_tracing() {
+  }
+
+  final public void disable_tracing() {
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java
new file mode 100644
index 0000000..2c203db
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserConstants.java
@@ -0,0 +1,86 @@
+/* Generated By:JavaCC: Do not edit this line. DateTimeParserConstants.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.datetime.parser;
+
+public interface DateTimeParserConstants {
+
+  int EOF = 0;
+  int OFFSETDIR = 24;
+  int MILITARY_ZONE = 35;
+  int WS = 36;
+  int COMMENT = 38;
+  int DIGITS = 46;
+  int QUOTEDPAIR = 47;
+  int ANY = 48;
+
+  int DEFAULT = 0;
+  int INCOMMENT = 1;
+  int NESTED_COMMENT = 2;
+
+  String[] tokenImage = {
+    "<EOF>",
+    "\"\\r\"",
+    "\"\\n\"",
+    "\",\"",
+    "\"Mon\"",
+    "\"Tue\"",
+    "\"Wed\"",
+    "\"Thu\"",
+    "\"Fri\"",
+    "\"Sat\"",
+    "\"Sun\"",
+    "\"Jan\"",
+    "\"Feb\"",
+    "\"Mar\"",
+    "\"Apr\"",
+    "\"May\"",
+    "\"Jun\"",
+    "\"Jul\"",
+    "\"Aug\"",
+    "\"Sep\"",
+    "\"Oct\"",
+    "\"Nov\"",
+    "\"Dec\"",
+    "\":\"",
+    "<OFFSETDIR>",
+    "\"UT\"",
+    "\"GMT\"",
+    "\"EST\"",
+    "\"EDT\"",
+    "\"CST\"",
+    "\"CDT\"",
+    "\"MST\"",
+    "\"MDT\"",
+    "\"PST\"",
+    "\"PDT\"",
+    "<MILITARY_ZONE>",
+    "<WS>",
+    "\"(\"",
+    "\")\"",
+    "<token of kind 39>",
+    "\"(\"",
+    "<token of kind 41>",
+    "<token of kind 42>",
+    "\"(\"",
+    "\")\"",
+    "<token of kind 45>",
+    "<DIGITS>",
+    "<QUOTEDPAIR>",
+    "<ANY>",
+  };
+
+}
diff --git a/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java
new file mode 100644
index 0000000..4b2d2fd
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/datetime/parser/DateTimeParserTokenManager.java
@@ -0,0 +1,882 @@
+/* Generated By:JavaCC: Do not edit this line. DateTimeParserTokenManager.java */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.datetime.parser;
+import org.apache.james.mime4j.field.datetime.DateTime;
+import java.util.Calendar;
+
+public class DateTimeParserTokenManager implements DateTimeParserConstants
+{
+        // Keeps track of how many levels of comment nesting
+        // we've encountered.  This is only used when the 2nd
+        // level is reached, for example ((this)), not (this).
+        // This is because the outermost level must be treated
+        // specially anyway, because the outermost ")" has a
+        // different token type than inner ")" instances.
+        static int commentNest;
+  public  java.io.PrintStream debugStream = System.out;
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      case 0:
+         if ((active0 & 0x7fe7cf7f0L) != 0L)
+         {
+            jjmatchedKind = 35;
+            return -1;
+         }
+         return -1;
+      case 1:
+         if ((active0 & 0x7fe7cf7f0L) != 0L)
+         {
+            if (jjmatchedPos == 0)
+            {
+               jjmatchedKind = 35;
+               jjmatchedPos = 0;
+            }
+            return -1;
+         }
+         return -1;
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private final int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private final int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 10:
+         return jjStopAtPos(0, 2);
+      case 13:
+         return jjStopAtPos(0, 1);
+      case 40:
+         return jjStopAtPos(0, 37);
+      case 44:
+         return jjStopAtPos(0, 3);
+      case 58:
+         return jjStopAtPos(0, 23);
+      case 65:
+         return jjMoveStringLiteralDfa1_0(0x44000L);
+      case 67:
+         return jjMoveStringLiteralDfa1_0(0x60000000L);
+      case 68:
+         return jjMoveStringLiteralDfa1_0(0x400000L);
+      case 69:
+         return jjMoveStringLiteralDfa1_0(0x18000000L);
+      case 70:
+         return jjMoveStringLiteralDfa1_0(0x1100L);
+      case 71:
+         return jjMoveStringLiteralDfa1_0(0x4000000L);
+      case 74:
+         return jjMoveStringLiteralDfa1_0(0x30800L);
+      case 77:
+         return jjMoveStringLiteralDfa1_0(0x18000a010L);
+      case 78:
+         return jjMoveStringLiteralDfa1_0(0x200000L);
+      case 79:
+         return jjMoveStringLiteralDfa1_0(0x100000L);
+      case 80:
+         return jjMoveStringLiteralDfa1_0(0x600000000L);
+      case 83:
+         return jjMoveStringLiteralDfa1_0(0x80600L);
+      case 84:
+         return jjMoveStringLiteralDfa1_0(0xa0L);
+      case 85:
+         return jjMoveStringLiteralDfa1_0(0x2000000L);
+      case 87:
+         return jjMoveStringLiteralDfa1_0(0x40L);
+      default :
+         return jjMoveNfa_0(0, 0);
+   }
+}
+private final int jjMoveStringLiteralDfa1_0(long active0)
+{
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(0, active0);
+      return 1;
+   }
+   switch(curChar)
+   {
+      case 68:
+         return jjMoveStringLiteralDfa2_0(active0, 0x550000000L);
+      case 77:
+         return jjMoveStringLiteralDfa2_0(active0, 0x4000000L);
+      case 83:
+         return jjMoveStringLiteralDfa2_0(active0, 0x2a8000000L);
+      case 84:
+         if ((active0 & 0x2000000L) != 0L)
+            return jjStopAtPos(1, 25);
+         break;
+      case 97:
+         return jjMoveStringLiteralDfa2_0(active0, 0xaa00L);
+      case 99:
+         return jjMoveStringLiteralDfa2_0(active0, 0x100000L);
+      case 101:
+         return jjMoveStringLiteralDfa2_0(active0, 0x481040L);
+      case 104:
+         return jjMoveStringLiteralDfa2_0(active0, 0x80L);
+      case 111:
+         return jjMoveStringLiteralDfa2_0(active0, 0x200010L);
+      case 112:
+         return jjMoveStringLiteralDfa2_0(active0, 0x4000L);
+      case 114:
+         return jjMoveStringLiteralDfa2_0(active0, 0x100L);
+      case 117:
+         return jjMoveStringLiteralDfa2_0(active0, 0x70420L);
+      default :
+         break;
+   }
+   return jjStartNfa_0(0, active0);
+}
+private final int jjMoveStringLiteralDfa2_0(long old0, long active0)
+{
+   if (((active0 &= old0)) == 0L)
+      return jjStartNfa_0(0, old0); 
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(1, active0);
+      return 2;
+   }
+   switch(curChar)
+   {
+      case 84:
+         if ((active0 & 0x4000000L) != 0L)
+            return jjStopAtPos(2, 26);
+         else if ((active0 & 0x8000000L) != 0L)
+            return jjStopAtPos(2, 27);
+         else if ((active0 & 0x10000000L) != 0L)
+            return jjStopAtPos(2, 28);
+         else if ((active0 & 0x20000000L) != 0L)
+            return jjStopAtPos(2, 29);
+         else if ((active0 & 0x40000000L) != 0L)
+            return jjStopAtPos(2, 30);
+         else if ((active0 & 0x80000000L) != 0L)
+            return jjStopAtPos(2, 31);
+         else if ((active0 & 0x100000000L) != 0L)
+            return jjStopAtPos(2, 32);
+         else if ((active0 & 0x200000000L) != 0L)
+            return jjStopAtPos(2, 33);
+         else if ((active0 & 0x400000000L) != 0L)
+            return jjStopAtPos(2, 34);
+         break;
+      case 98:
+         if ((active0 & 0x1000L) != 0L)
+            return jjStopAtPos(2, 12);
+         break;
+      case 99:
+         if ((active0 & 0x400000L) != 0L)
+            return jjStopAtPos(2, 22);
+         break;
+      case 100:
+         if ((active0 & 0x40L) != 0L)
+            return jjStopAtPos(2, 6);
+         break;
+      case 101:
+         if ((active0 & 0x20L) != 0L)
+            return jjStopAtPos(2, 5);
+         break;
+      case 103:
+         if ((active0 & 0x40000L) != 0L)
+            return jjStopAtPos(2, 18);
+         break;
+      case 105:
+         if ((active0 & 0x100L) != 0L)
+            return jjStopAtPos(2, 8);
+         break;
+      case 108:
+         if ((active0 & 0x20000L) != 0L)
+            return jjStopAtPos(2, 17);
+         break;
+      case 110:
+         if ((active0 & 0x10L) != 0L)
+            return jjStopAtPos(2, 4);
+         else if ((active0 & 0x400L) != 0L)
+            return jjStopAtPos(2, 10);
+         else if ((active0 & 0x800L) != 0L)
+            return jjStopAtPos(2, 11);
+         else if ((active0 & 0x10000L) != 0L)
+            return jjStopAtPos(2, 16);
+         break;
+      case 112:
+         if ((active0 & 0x80000L) != 0L)
+            return jjStopAtPos(2, 19);
+         break;
+      case 114:
+         if ((active0 & 0x2000L) != 0L)
+            return jjStopAtPos(2, 13);
+         else if ((active0 & 0x4000L) != 0L)
+            return jjStopAtPos(2, 14);
+         break;
+      case 116:
+         if ((active0 & 0x200L) != 0L)
+            return jjStopAtPos(2, 9);
+         else if ((active0 & 0x100000L) != 0L)
+            return jjStopAtPos(2, 20);
+         break;
+      case 117:
+         if ((active0 & 0x80L) != 0L)
+            return jjStopAtPos(2, 7);
+         break;
+      case 118:
+         if ((active0 & 0x200000L) != 0L)
+            return jjStopAtPos(2, 21);
+         break;
+      case 121:
+         if ((active0 & 0x8000L) != 0L)
+            return jjStopAtPos(2, 15);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_0(1, active0);
+}
+private final void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private final void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private final void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+private final void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+private final void jjCheckNAddStates(int start)
+{
+   jjCheckNAdd(jjnextStates[start]);
+   jjCheckNAdd(jjnextStates[start + 1]);
+}
+private final int jjMoveNfa_0(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 4;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 46)
+                        kind = 46;
+                     jjCheckNAdd(3);
+                  }
+                  else if ((0x100000200L & l) != 0L)
+                  {
+                     if (kind > 36)
+                        kind = 36;
+                     jjCheckNAdd(2);
+                  }
+                  else if ((0x280000000000L & l) != 0L)
+                  {
+                     if (kind > 24)
+                        kind = 24;
+                  }
+                  break;
+               case 2:
+                  if ((0x100000200L & l) == 0L)
+                     break;
+                  kind = 36;
+                  jjCheckNAdd(2);
+                  break;
+               case 3:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  kind = 46;
+                  jjCheckNAdd(3);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x7fffbfe07fffbfeL & l) != 0L)
+                     kind = 35;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 4 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_1(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_1(int pos, long active0)
+{
+   return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_1(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_1(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_1()
+{
+   switch(curChar)
+   {
+      case 40:
+         return jjStopAtPos(0, 40);
+      case 41:
+         return jjStopAtPos(0, 38);
+      default :
+         return jjMoveNfa_1(0, 0);
+   }
+}
+static final long[] jjbitVec0 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private final int jjMoveNfa_1(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 41)
+                     kind = 41;
+                  break;
+               case 1:
+                  if (kind > 39)
+                     kind = 39;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 41)
+                     kind = 41;
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 39)
+                     kind = 39;
+                  break;
+               case 2:
+                  if (kind > 41)
+                     kind = 41;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 41)
+                     kind = 41;
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 39)
+                     kind = 39;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+private final int jjStopStringLiteralDfa_2(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_2(int pos, long active0)
+{
+   return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0), pos + 1);
+}
+private final int jjStartNfaWithStates_2(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_2(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_2()
+{
+   switch(curChar)
+   {
+      case 40:
+         return jjStopAtPos(0, 43);
+      case 41:
+         return jjStopAtPos(0, 44);
+      default :
+         return jjMoveNfa_2(0, 0);
+   }
+}
+private final int jjMoveNfa_2(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 3;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 45)
+                     kind = 45;
+                  break;
+               case 1:
+                  if (kind > 42)
+                     kind = 42;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if (kind > 45)
+                     kind = 45;
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 1:
+                  if (kind > 42)
+                     kind = 42;
+                  break;
+               case 2:
+                  if (kind > 45)
+                     kind = 45;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 45)
+                     kind = 45;
+                  break;
+               case 1:
+                  if ((jjbitVec0[i2] & l2) != 0L && kind > 42)
+                     kind = 42;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 3 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+};
+public static final String[] jjstrLiteralImages = {
+"", "\15", "\12", "\54", "\115\157\156", "\124\165\145", "\127\145\144", 
+"\124\150\165", "\106\162\151", "\123\141\164", "\123\165\156", "\112\141\156", 
+"\106\145\142", "\115\141\162", "\101\160\162", "\115\141\171", "\112\165\156", 
+"\112\165\154", "\101\165\147", "\123\145\160", "\117\143\164", "\116\157\166", 
+"\104\145\143", "\72", null, "\125\124", "\107\115\124", "\105\123\124", "\105\104\124", 
+"\103\123\124", "\103\104\124", "\115\123\124", "\115\104\124", "\120\123\124", 
+"\120\104\124", null, null, null, null, null, null, null, null, null, null, null, null, null, 
+null, };
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+   "INCOMMENT", 
+   "NESTED_COMMENT", 
+};
+public static final int[] jjnewLexState = {
+   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
+   -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 0, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, 
+};
+static final long[] jjtoToken = {
+   0x400fffffffffL, 
+};
+static final long[] jjtoSkip = {
+   0x5000000000L, 
+};
+static final long[] jjtoSpecial = {
+   0x1000000000L, 
+};
+static final long[] jjtoMore = {
+   0x3fa000000000L, 
+};
+protected SimpleCharStream input_stream;
+private final int[] jjrounds = new int[4];
+private final int[] jjstateSet = new int[8];
+StringBuffer image;
+int jjimageLen;
+int lengthOfMatch;
+protected char curChar;
+public DateTimeParserTokenManager(SimpleCharStream stream){
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+public DateTimeParserTokenManager(SimpleCharStream stream, int lexState){
+   this(stream);
+   SwitchTo(lexState);
+}
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private final void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 4; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 3 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+   Token t = Token.newToken(jjmatchedKind);
+   t.kind = jjmatchedKind;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   t.image = (im == null) ? input_stream.GetImage() : im;
+   t.beginLine = input_stream.getBeginLine();
+   t.beginColumn = input_stream.getBeginColumn();
+   t.endLine = input_stream.getEndLine();
+   t.endColumn = input_stream.getEndColumn();
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+public Token getNextToken() 
+{
+  int kind;
+  Token specialToken = null;
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      matchedToken.specialToken = specialToken;
+      return matchedToken;
+   }
+   image = null;
+   jjimageLen = 0;
+
+   for (;;)
+   {
+     switch(curLexState)
+     {
+       case 0:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_0();
+         break;
+       case 1:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_1();
+         break;
+       case 2:
+         jjmatchedKind = 0x7fffffff;
+         jjmatchedPos = 0;
+         curPos = jjMoveStringLiteralDfa0_2();
+         break;
+     }
+     if (jjmatchedKind != 0x7fffffff)
+     {
+        if (jjmatchedPos + 1 < curPos)
+           input_stream.backup(curPos - jjmatchedPos - 1);
+        if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+        {
+           matchedToken = jjFillToken();
+           matchedToken.specialToken = specialToken;
+       if (jjnewLexState[jjmatchedKind] != -1)
+         curLexState = jjnewLexState[jjmatchedKind];
+           return matchedToken;
+        }
+        else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+        {
+           if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+           {
+              matchedToken = jjFillToken();
+              if (specialToken == null)
+                 specialToken = matchedToken;
+              else
+              {
+                 matchedToken.specialToken = specialToken;
+                 specialToken = (specialToken.next = matchedToken);
+              }
+           }
+         if (jjnewLexState[jjmatchedKind] != -1)
+           curLexState = jjnewLexState[jjmatchedKind];
+           continue EOFLoop;
+        }
+        MoreLexicalActions();
+      if (jjnewLexState[jjmatchedKind] != -1)
+        curLexState = jjnewLexState[jjmatchedKind];
+        curPos = 0;
+        jjmatchedKind = 0x7fffffff;
+        try {
+           curChar = input_stream.readChar();
+           continue;
+        }
+        catch (java.io.IOException e1) { }
+     }
+     int error_line = input_stream.getEndLine();
+     int error_column = input_stream.getEndColumn();
+     String error_after = null;
+     boolean EOFSeen = false;
+     try { input_stream.readChar(); input_stream.backup(1); }
+     catch (java.io.IOException e1) {
+        EOFSeen = true;
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+        if (curChar == '\n' || curChar == '\r') {
+           error_line++;
+           error_column = 0;
+        }
+        else
+           error_column++;
+     }
+     if (!EOFSeen) {
+        input_stream.backup(1);
+        error_after = curPos <= 1 ? "" : input_stream.GetImage();
+     }
+     throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+   }
+  }
+}
+
+void MoreLexicalActions()
+{
+   jjimageLen += (lengthOfMatch = jjmatchedPos + 1);
+   switch(jjmatchedKind)
+   {
+      case 39 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      case 40 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              commentNest = 1;
+         break;
+      case 42 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+                          image.deleteCharAt(image.length() - 2);
+         break;
+      case 43 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              ++commentNest;
+         break;
+      case 44 :
+         if (image == null)
+            image = new StringBuffer();
+         image.append(input_stream.GetSuffix(jjimageLen));
+         jjimageLen = 0;
+              --commentNest; if (commentNest == 0) SwitchTo(INCOMMENT);
+         break;
+      default : 
+         break;
+   }
+}
+}
diff --git a/src/org/apache/james/mime4j/field/datetime/parser/ParseException.java b/src/org/apache/james/mime4j/field/datetime/parser/ParseException.java
new file mode 100644
index 0000000..13b3ff0
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/datetime/parser/ParseException.java
@@ -0,0 +1,207 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.datetime.parser;
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+  /**
+   * This constructor is used by the method "generateParseException"
+   * in the generated parser.  Calling this constructor generates
+   * a new object of this type with the fields "currentToken",
+   * "expectedTokenSequences", and "tokenImage" set.  The boolean
+   * flag "specialConstructor" is also set to true to indicate that
+   * this constructor was used to create this object.
+   * This constructor calls its super class with the empty string
+   * to force the "toString" method of parent class "Throwable" to
+   * print the error message in the form:
+   *     ParseException: <result of getMessage>
+   */
+  public ParseException(Token currentTokenVal,
+                        int[][] expectedTokenSequencesVal,
+                        String[] tokenImageVal
+                       )
+  {
+    super("");
+    specialConstructor = true;
+    currentToken = currentTokenVal;
+    expectedTokenSequences = expectedTokenSequencesVal;
+    tokenImage = tokenImageVal;
+  }
+
+  /**
+   * The following constructors are for use by you for whatever
+   * purpose you can think of.  Constructing the exception in this
+   * manner makes the exception behave in the normal way - i.e., as
+   * documented in the class "Throwable".  The fields "errorToken",
+   * "expectedTokenSequences", and "tokenImage" do not contain
+   * relevant information.  The JavaCC generated code does not use
+   * these constructors.
+   */
+
+  public ParseException() {
+    super();
+    specialConstructor = false;
+  }
+
+  public ParseException(String message) {
+    super(message);
+    specialConstructor = false;
+  }
+
+  /**
+   * This variable determines which constructor was used to create
+   * this object and thereby affects the semantics of the
+   * "getMessage" method (see below).
+   */
+  protected boolean specialConstructor;
+
+  /**
+   * This is the last token that has been consumed successfully.  If
+   * this object has been created due to a parse error, the token
+   * followng this token will (therefore) be the first error token.
+   */
+  public Token currentToken;
+
+  /**
+   * Each entry in this array is an array of integers.  Each array
+   * of integers represents a sequence of tokens (by their ordinal
+   * values) that is expected at this point of the parse.
+   */
+  public int[][] expectedTokenSequences;
+
+  /**
+   * This is a reference to the "tokenImage" array of the generated
+   * parser within which the parse error occurred.  This array is
+   * defined in the generated ...Constants interface.
+   */
+  public String[] tokenImage;
+
+  /**
+   * This method has the standard behavior when this object has been
+   * created using the standard constructors.  Otherwise, it uses
+   * "currentToken" and "expectedTokenSequences" to generate a parse
+   * error message and returns it.  If this object has been created
+   * due to a parse error, and you do not catch it (it gets thrown
+   * from the parser), then this method is called during the printing
+   * of the final stack trace, and hence the correct error message
+   * gets displayed.
+   */
+  public String getMessage() {
+    if (!specialConstructor) {
+      return super.getMessage();
+    }
+    StringBuffer expected = new StringBuffer();
+    int maxSize = 0;
+    for (int i = 0; i < expectedTokenSequences.length; i++) {
+      if (maxSize < expectedTokenSequences[i].length) {
+        maxSize = expectedTokenSequences[i].length;
+      }
+      for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+        expected.append(tokenImage[expectedTokenSequences[i][j]]).append(" ");
+      }
+      if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+        expected.append("...");
+      }
+      expected.append(eol).append("    ");
+    }
+    String retval = "Encountered \"";
+    Token tok = currentToken.next;
+    for (int i = 0; i < maxSize; i++) {
+      if (i != 0) retval += " ";
+      if (tok.kind == 0) {
+        retval += tokenImage[0];
+        break;
+      }
+      retval += add_escapes(tok.image);
+      tok = tok.next; 
+    }
+    retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+    retval += "." + eol;
+    if (expectedTokenSequences.length == 1) {
+      retval += "Was expecting:" + eol + "    ";
+    } else {
+      retval += "Was expecting one of:" + eol + "    ";
+    }
+    retval += expected.toString();
+    return retval;
+  }
+
+  /**
+   * The end of line string for this machine.
+   */
+  protected String eol = System.getProperty("line.separator", "\n");
+ 
+  /**
+   * Used to convert raw characters to their escaped version
+   * when these raw version cannot be used as part of an ASCII
+   * string literal.
+   */
+  protected String add_escapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+}
diff --git a/src/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java b/src/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java
new file mode 100644
index 0000000..2724529
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/datetime/parser/SimpleCharStream.java
@@ -0,0 +1,454 @@
+/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.datetime.parser;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).
+ */
+
+public class SimpleCharStream
+{
+  public static final boolean staticFlag = false;
+  int bufsize;
+  int available;
+  int tokenBegin;
+  public int bufpos = -1;
+  protected int bufline[];
+  protected int bufcolumn[];
+
+  protected int column = 0;
+  protected int line = 1;
+
+  protected boolean prevCharIsCR = false;
+  protected boolean prevCharIsLF = false;
+
+  protected java.io.Reader inputStream;
+
+  protected char[] buffer;
+  protected int maxNextCharInd = 0;
+  protected int inBuf = 0;
+  protected int tabSize = 8;
+
+  protected void setTabSize(int i) { tabSize = i; }
+  protected int getTabSize(int i) { return tabSize; }
+
+
+  protected void ExpandBuff(boolean wrapAround)
+  {
+     char[] newbuffer = new char[bufsize + 2048];
+     int newbufline[] = new int[bufsize + 2048];
+     int newbufcolumn[] = new int[bufsize + 2048];
+
+     try
+     {
+        if (wrapAround)
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           System.arraycopy(buffer, 0, newbuffer,
+                                             bufsize - tokenBegin, bufpos);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+        }
+        else
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos -= tokenBegin);
+        }
+     }
+     catch (Throwable t)
+     {
+        throw new Error(t.getMessage());
+     }
+
+
+     bufsize += 2048;
+     available = bufsize;
+     tokenBegin = 0;
+  }
+
+  protected void FillBuff() throws java.io.IOException
+  {
+     if (maxNextCharInd == available)
+     {
+        if (available == bufsize)
+        {
+           if (tokenBegin > 2048)
+           {
+              bufpos = maxNextCharInd = 0;
+              available = tokenBegin;
+           }
+           else if (tokenBegin < 0)
+              bufpos = maxNextCharInd = 0;
+           else
+              ExpandBuff(false);
+        }
+        else if (available > tokenBegin)
+           available = bufsize;
+        else if ((tokenBegin - available) < 2048)
+           ExpandBuff(true);
+        else
+           available = tokenBegin;
+     }
+
+     int i;
+     try {
+        if ((i = inputStream.read(buffer, maxNextCharInd,
+                                    available - maxNextCharInd)) == -1)
+        {
+           inputStream.close();
+           throw new java.io.IOException();
+        }
+        else
+           maxNextCharInd += i;
+        return;
+     }
+     catch(java.io.IOException e) {
+        --bufpos;
+        backup(0);
+        if (tokenBegin == -1)
+           tokenBegin = bufpos;
+        throw e;
+     }
+  }
+
+  public char BeginToken() throws java.io.IOException
+  {
+     tokenBegin = -1;
+     char c = readChar();
+     tokenBegin = bufpos;
+
+     return c;
+  }
+
+  protected void UpdateLineColumn(char c)
+  {
+     column++;
+
+     if (prevCharIsLF)
+     {
+        prevCharIsLF = false;
+        line += (column = 1);
+     }
+     else if (prevCharIsCR)
+     {
+        prevCharIsCR = false;
+        if (c == '\n')
+        {
+           prevCharIsLF = true;
+        }
+        else
+           line += (column = 1);
+     }
+
+     switch (c)
+     {
+        case '\r' :
+           prevCharIsCR = true;
+           break;
+        case '\n' :
+           prevCharIsLF = true;
+           break;
+        case '\t' :
+           column--;
+           column += (tabSize - (column % tabSize));
+           break;
+        default :
+           break;
+     }
+
+     bufline[bufpos] = line;
+     bufcolumn[bufpos] = column;
+  }
+
+  public char readChar() throws java.io.IOException
+  {
+     if (inBuf > 0)
+     {
+        --inBuf;
+
+        if (++bufpos == bufsize)
+           bufpos = 0;
+
+        return buffer[bufpos];
+     }
+
+     if (++bufpos >= maxNextCharInd)
+        FillBuff();
+
+     char c = buffer[bufpos];
+
+     UpdateLineColumn(c);
+     return (c);
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndColumn
+   */
+  @Deprecated
+  public int getColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndLine
+   */
+  @Deprecated
+  public int getLine() {
+     return bufline[bufpos];
+  }
+
+  public int getEndColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  public int getEndLine() {
+     return bufline[bufpos];
+  }
+
+  public int getBeginColumn() {
+     return bufcolumn[tokenBegin];
+  }
+
+  public int getBeginLine() {
+     return bufline[tokenBegin];
+  }
+
+  public void backup(int amount) {
+
+    inBuf += amount;
+    if ((bufpos -= amount) < 0)
+       bufpos += bufsize;
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    available = bufsize = buffersize;
+    buffer = new char[buffersize];
+    bufline = new int[buffersize];
+    bufcolumn = new int[buffersize];
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+                          int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.Reader dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    if (buffer == null || buffersize != buffer.length)
+    {
+      available = bufsize = buffersize;
+      buffer = new char[buffersize];
+      bufline = new int[buffersize];
+      bufcolumn = new int[buffersize];
+    }
+    prevCharIsLF = prevCharIsCR = false;
+    tokenBegin = inBuf = maxNextCharInd = 0;
+    bufpos = -1;
+  }
+
+  public void ReInit(java.io.Reader dstream, int startline,
+                     int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+
+  public void ReInit(java.io.Reader dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+  int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+     this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+     this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+     this(dstream, encoding, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+                          int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+     this(dstream, encoding, 1, 1, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  public void ReInit(java.io.InputStream dstream, int startline,
+                          int startcolumn, int buffersize)
+  {
+     ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(dstream, encoding, 1, 1, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                     int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(dstream, encoding, startline, startcolumn, 4096);
+  }
+  public void ReInit(java.io.InputStream dstream, int startline,
+                     int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+  public String GetImage()
+  {
+     if (bufpos >= tokenBegin)
+        return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+     else
+        return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+                              new String(buffer, 0, bufpos + 1);
+  }
+
+  public char[] GetSuffix(int len)
+  {
+     char[] ret = new char[len];
+
+     if ((bufpos + 1) >= len)
+        System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+     else
+     {
+        System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+                                                          len - bufpos - 1);
+        System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+     }
+
+     return ret;
+  }
+
+  public void Done()
+  {
+     buffer = null;
+     bufline = null;
+     bufcolumn = null;
+  }
+
+  /**
+   * Method to adjust line and column numbers for the start of a token.
+   */
+  public void adjustBeginLineColumn(int newLine, int newCol)
+  {
+     int start = tokenBegin;
+     int len;
+
+     if (bufpos >= tokenBegin)
+     {
+        len = bufpos - tokenBegin + inBuf + 1;
+     }
+     else
+     {
+        len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+     }
+
+     int i = 0, j = 0, k = 0;
+     int nextColDiff = 0, columnDiff = 0;
+
+     while (i < len &&
+            bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+     {
+        bufline[j] = newLine;
+        nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+        bufcolumn[j] = newCol + columnDiff;
+        columnDiff = nextColDiff;
+        i++;
+     } 
+
+     if (i < len)
+     {
+        bufline[j] = newLine++;
+        bufcolumn[j] = newCol + columnDiff;
+
+        while (i++ < len)
+        {
+           if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+              bufline[j] = newLine++;
+           else
+              bufline[j] = newLine;
+        }
+     }
+
+     line = bufline[j];
+     column = bufcolumn[j];
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/datetime/parser/Token.java b/src/org/apache/james/mime4j/field/datetime/parser/Token.java
new file mode 100644
index 0000000..0927a09
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/datetime/parser/Token.java
@@ -0,0 +1,96 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.datetime.parser;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token {
+
+  /**
+   * An integer that describes the kind of this token.  This numbering
+   * system is determined by JavaCCParser, and a table of these numbers is
+   * stored in the file ...Constants.java.
+   */
+  public int kind;
+
+  /**
+   * beginLine and beginColumn describe the position of the first character
+   * of this token; endLine and endColumn describe the position of the
+   * last character of this token.
+   */
+  public int beginLine, beginColumn, endLine, endColumn;
+
+  /**
+   * The string image of the token.
+   */
+  public String image;
+
+  /**
+   * A reference to the next regular (non-special) token from the input
+   * stream.  If this is the last token from the input stream, or if the
+   * token manager has not read tokens beyond this one, this field is
+   * set to null.  This is true only if this token is also a regular
+   * token.  Otherwise, see below for a description of the contents of
+   * this field.
+   */
+  public Token next;
+
+  /**
+   * This field is used to access special tokens that occur prior to this
+   * token, but after the immediately preceding regular (non-special) token.
+   * If there are no such special tokens, this field is set to null.
+   * When there are more than one such special token, this field refers
+   * to the last of these special tokens, which in turn refers to the next
+   * previous special token through its specialToken field, and so on
+   * until the first special token (whose specialToken field is null).
+   * The next fields of special tokens refer to other special tokens that
+   * immediately follow it (without an intervening regular token).  If there
+   * is no such token, this field is null.
+   */
+  public Token specialToken;
+
+  /**
+   * Returns the image.
+   */
+  public String toString()
+  {
+     return image;
+  }
+
+  /**
+   * Returns a new Token object, by default. However, if you want, you
+   * can create and return subclass objects based on the value of ofKind.
+   * Simply add the cases to the switch for all those special cases.
+   * For example, if you have a subclass of Token called IDToken that
+   * you want to create if ofKind is ID, simlpy add something like :
+   *
+   *    case MyParserConstants.ID : return new IDToken();
+   *
+   * to the following switch statement. Then you can cast matchedToken
+   * variable to the appropriate type and use it in your lexical actions.
+   */
+  public static final Token newToken(int ofKind)
+  {
+     switch(ofKind)
+     {
+       default : return new Token();
+     }
+  }
+
+}
diff --git a/src/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java b/src/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java
new file mode 100644
index 0000000..e7043c1
--- /dev/null
+++ b/src/org/apache/james/mime4j/field/datetime/parser/TokenMgrError.java
@@ -0,0 +1,148 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 3.0 */
+/*
+ *  Copyright 2004 the mime4j project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.james.mime4j.field.datetime.parser;
+
+public class TokenMgrError extends Error
+{
+   /*
+    * Ordinals for various reasons why an Error of this type can be thrown.
+    */
+
+   /**
+    * Lexical error occured.
+    */
+   static final int LEXICAL_ERROR = 0;
+
+   /**
+    * An attempt wass made to create a second instance of a static token manager.
+    */
+   static final int STATIC_LEXER_ERROR = 1;
+
+   /**
+    * Tried to change to an invalid lexical state.
+    */
+   static final int INVALID_LEXICAL_STATE = 2;
+
+   /**
+    * Detected (and bailed out of) an infinite loop in the token manager.
+    */
+   static final int LOOP_DETECTED = 3;
+
+   /**
+    * Indicates the reason why the exception is thrown. It will have
+    * one of the above 4 values.
+    */
+   int errorCode;
+
+   /**
+    * Replaces unprintable characters by their espaced (or unicode escaped)
+    * equivalents in the given string
+    */
+   protected static final String addEscapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+   /**
+    * Returns a detailed message for the Error when it is thrown by the
+    * token manager to indicate a lexical error.
+    * Parameters : 
+    *    EOFSeen     : indicates if EOF caused the lexicl error
+    *    curLexState : lexical state in which this error occured
+    *    errorLine   : line number when the error occured
+    *    errorColumn : column number when the error occured
+    *    errorAfter  : prefix that was seen before this error occured
+    *    curchar     : the offending character
+    * Note: You can customize the lexical error message by modifying this method.
+    */
+   protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+      return("Lexical error at line " +
+           errorLine + ", column " +
+           errorColumn + ".  Encountered: " +
+           (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+           "after : \"" + addEscapes(errorAfter) + "\"");
+   }
+
+   /**
+    * You can also modify the body of this method to customize your error messages.
+    * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+    * of end-users concern, so you can return something like : 
+    *
+    *     "Internal Error : Please file a bug report .... "
+    *
+    * from this method for such cases in the release version of your parser.
+    */
+   public String getMessage() {
+      return super.getMessage();
+   }
+
+   /*
+    * Constructors of various flavors follow.
+    */
+
+   public TokenMgrError() {
+   }
+
+   public TokenMgrError(String message, int reason) {
+      super(message);
+      errorCode = reason;
+   }
+
+   public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+      this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+   }
+}
diff --git a/src/org/apache/james/mime4j/util/CharsetUtil.java b/src/org/apache/james/mime4j/util/CharsetUtil.java
new file mode 100644
index 0000000..4e712fc
--- /dev/null
+++ b/src/org/apache/james/mime4j/util/CharsetUtil.java
@@ -0,0 +1,1249 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.util;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.TreeSet;
+
+//BEGIN android-changed: Stubbing out logging
+import org.apache.james.mime4j.Log;
+import org.apache.james.mime4j.LogFactory;
+//END android-changed
+
+/**
+ * Utility class for working with character sets. It is somewhat similar to
+ * the Java 1.4 <code>java.nio.charset.Charset</code> class but knows many
+ * more aliases and is compatible with Java 1.3. It will use a simple detection
+ * mechanism to detect what character sets the current VM supports. This will
+ * be a sub-set of the character sets listed in the
+ * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">
+ * Java 1.5 (J2SE5.0) Supported Encodings</a> document.
+ * <p>
+ * The <a href="http://www.iana.org/assignments/character-sets">
+ * IANA Character Sets</a> document has been used to determine the preferred
+ * MIME character set names and to get a list of known aliases.
+ * <p>
+ * This is a complete list of the character sets known to this class:
+ * <table>
+ *     <tr>
+ *         <td>Canonical (Java) name</td>
+ *         <td>MIME preferred</td>
+ *         <td>Aliases</td>
+ *     </tr>
+ *     <tr>
+ *         <td>ASCII</td>
+ *         <td>US-ASCII</td>
+ *         <td>ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ISO646-US us IBM367 cp367 csASCII ascii7 646 iso_646.irv:1983 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Big5</td>
+ *         <td>Big5</td>
+ *         <td>csBig5 CN-Big5 BIG-FIVE BIGFIVE </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Big5_HKSCS</td>
+ *         <td>Big5-HKSCS</td>
+ *         <td>big5hkscs </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Big5_Solaris</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp037</td>
+ *         <td>IBM037</td>
+ *         <td>ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl csIBM037 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1006</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1025</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1026</td>
+ *         <td>IBM1026</td>
+ *         <td>csIBM1026 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1046</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1047</td>
+ *         <td>IBM1047</td>
+ *         <td>IBM-1047 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1097</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1098</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1112</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1122</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1123</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1124</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1140</td>
+ *         <td>IBM01140</td>
+ *         <td>CCSID01140 CP01140 ebcdic-us-37+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1141</td>
+ *         <td>IBM01141</td>
+ *         <td>CCSID01141 CP01141 ebcdic-de-273+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1142</td>
+ *         <td>IBM01142</td>
+ *         <td>CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1143</td>
+ *         <td>IBM01143</td>
+ *         <td>CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1144</td>
+ *         <td>IBM01144</td>
+ *         <td>CCSID01144 CP01144 ebcdic-it-280+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1145</td>
+ *         <td>IBM01145</td>
+ *         <td>CCSID01145 CP01145 ebcdic-es-284+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1146</td>
+ *         <td>IBM01146</td>
+ *         <td>CCSID01146 CP01146 ebcdic-gb-285+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1147</td>
+ *         <td>IBM01147</td>
+ *         <td>CCSID01147 CP01147 ebcdic-fr-297+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1148</td>
+ *         <td>IBM01148</td>
+ *         <td>CCSID01148 CP01148 ebcdic-international-500+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1149</td>
+ *         <td>IBM01149</td>
+ *         <td>CCSID01149 CP01149 ebcdic-is-871+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1250</td>
+ *         <td>windows-1250</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1251</td>
+ *         <td>windows-1251</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1252</td>
+ *         <td>windows-1252</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1253</td>
+ *         <td>windows-1253</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1254</td>
+ *         <td>windows-1254</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1255</td>
+ *         <td>windows-1255</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1256</td>
+ *         <td>windows-1256</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1257</td>
+ *         <td>windows-1257</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1258</td>
+ *         <td>windows-1258</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1381</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp1383</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp273</td>
+ *         <td>IBM273</td>
+ *         <td>csIBM273 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp277</td>
+ *         <td>IBM277</td>
+ *         <td>EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp278</td>
+ *         <td>IBM278</td>
+ *         <td>CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp280</td>
+ *         <td>IBM280</td>
+ *         <td>ebcdic-cp-it csIBM280 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp284</td>
+ *         <td>IBM284</td>
+ *         <td>ebcdic-cp-es csIBM284 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp285</td>
+ *         <td>IBM285</td>
+ *         <td>ebcdic-cp-gb csIBM285 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp297</td>
+ *         <td>IBM297</td>
+ *         <td>ebcdic-cp-fr csIBM297 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp33722</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp420</td>
+ *         <td>IBM420</td>
+ *         <td>ebcdic-cp-ar1 csIBM420 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp424</td>
+ *         <td>IBM424</td>
+ *         <td>ebcdic-cp-he csIBM424 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp437</td>
+ *         <td>IBM437</td>
+ *         <td>437 csPC8CodePage437 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp500</td>
+ *         <td>IBM500</td>
+ *         <td>ebcdic-cp-be ebcdic-cp-ch csIBM500 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp737</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp775</td>
+ *         <td>IBM775</td>
+ *         <td>csPC775Baltic </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp838</td>
+ *         <td>IBM-Thai</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp850</td>
+ *         <td>IBM850</td>
+ *         <td>850 csPC850Multilingual </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp852</td>
+ *         <td>IBM852</td>
+ *         <td>852 csPCp852 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp855</td>
+ *         <td>IBM855</td>
+ *         <td>855 csIBM855 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp856</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp857</td>
+ *         <td>IBM857</td>
+ *         <td>857 csIBM857 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp858</td>
+ *         <td>IBM00858</td>
+ *         <td>CCSID00858 CP00858 PC-Multilingual-850+euro </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp860</td>
+ *         <td>IBM860</td>
+ *         <td>860 csIBM860 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp861</td>
+ *         <td>IBM861</td>
+ *         <td>861 cp-is csIBM861 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp862</td>
+ *         <td>IBM862</td>
+ *         <td>862 csPC862LatinHebrew </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp863</td>
+ *         <td>IBM863</td>
+ *         <td>863 csIBM863 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp864</td>
+ *         <td>IBM864</td>
+ *         <td>cp864 csIBM864 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp865</td>
+ *         <td>IBM865</td>
+ *         <td>865 csIBM865 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp866</td>
+ *         <td>IBM866</td>
+ *         <td>866 csIBM866 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp868</td>
+ *         <td>IBM868</td>
+ *         <td>cp-ar csIBM868 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp869</td>
+ *         <td>IBM869</td>
+ *         <td>cp-gr csIBM869 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp870</td>
+ *         <td>IBM870</td>
+ *         <td>ebcdic-cp-roece ebcdic-cp-yu csIBM870 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp871</td>
+ *         <td>IBM871</td>
+ *         <td>ebcdic-cp-is csIBM871 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp875</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp918</td>
+ *         <td>IBM918</td>
+ *         <td>ebcdic-cp-ar2 csIBM918 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp921</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp922</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp930</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp933</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp935</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp937</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp939</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp942</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp942C</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp943</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp943C</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp948</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp949</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp949C</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp950</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp964</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>Cp970</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>EUC_CN</td>
+ *         <td>GB2312</td>
+ *         <td>x-EUC-CN csGB2312 euccn euc-cn gb2312-80 gb2312-1980 CN-GB CN-GB-ISOIR165 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>EUC_JP</td>
+ *         <td>EUC-JP</td>
+ *         <td>csEUCPkdFmtJapanese Extended_UNIX_Code_Packed_Format_for_Japanese eucjis x-eucjp eucjp x-euc-jp </td>
+ *     </tr>
+ *     <tr>
+ *         <td>EUC_JP_LINUX</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>EUC_JP_Solaris</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>EUC_KR</td>
+ *         <td>EUC-KR</td>
+ *         <td>csEUCKR ksc5601 5601 ksc5601_1987 ksc_5601 ksc5601-1987 ks_c_5601-1987 euckr </td>
+ *     </tr>
+ *     <tr>
+ *         <td>EUC_TW</td>
+ *         <td>EUC-TW</td>
+ *         <td>x-EUC-TW cns11643 euctw </td>
+ *     </tr>
+ *     <tr>
+ *         <td>GB18030</td>
+ *         <td>GB18030</td>
+ *         <td>gb18030-2000 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>GBK</td>
+ *         <td>windows-936</td>
+ *         <td>CP936 MS936 ms_936 x-mswin-936 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISCII91</td>
+ *         <td>?</td>
+ *         <td>x-ISCII91 iscii </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO2022CN</td>
+ *         <td>ISO-2022-CN</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO2022JP</td>
+ *         <td>ISO-2022-JP</td>
+ *         <td>csISO2022JP JIS jis_encoding csjisencoding </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO2022KR</td>
+ *         <td>ISO-2022-KR</td>
+ *         <td>csISO2022KR </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO2022_CN_CNS</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO2022_CN_GB</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_1</td>
+ *         <td>ISO-8859-1</td>
+ *         <td>ISO_8859-1:1987 iso-ir-100 ISO_8859-1 latin1 l1 IBM819 CP819 csISOLatin1 8859_1 819 IBM-819 ISO8859-1 ISO_8859_1 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_13</td>
+ *         <td>ISO-8859-13</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_15</td>
+ *         <td>ISO-8859-15</td>
+ *         <td>ISO_8859-15 Latin-9 8859_15 csISOlatin9 IBM923 cp923 923 L9 IBM-923 ISO8859-15 LATIN9 LATIN0 csISOlatin0 ISO8859_15_FDIS </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_2</td>
+ *         <td>ISO-8859-2</td>
+ *         <td>ISO_8859-2:1987 iso-ir-101 ISO_8859-2 latin2 l2 csISOLatin2 8859_2 iso8859_2 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_3</td>
+ *         <td>ISO-8859-3</td>
+ *         <td>ISO_8859-3:1988 iso-ir-109 ISO_8859-3 latin3 l3 csISOLatin3 8859_3 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_4</td>
+ *         <td>ISO-8859-4</td>
+ *         <td>ISO_8859-4:1988 iso-ir-110 ISO_8859-4 latin4 l4 csISOLatin4 8859_4 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_5</td>
+ *         <td>ISO-8859-5</td>
+ *         <td>ISO_8859-5:1988 iso-ir-144 ISO_8859-5 cyrillic csISOLatinCyrillic 8859_5 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_6</td>
+ *         <td>ISO-8859-6</td>
+ *         <td>ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ECMA-114 ASMO-708 arabic csISOLatinArabic 8859_6 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_7</td>
+ *         <td>ISO-8859-7</td>
+ *         <td>ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ELOT_928 ECMA-118 greek greek8 csISOLatinGreek 8859_7 sun_eu_greek </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_8</td>
+ *         <td>ISO-8859-8</td>
+ *         <td>ISO_8859-8:1988 iso-ir-138 ISO_8859-8 hebrew csISOLatinHebrew 8859_8 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>ISO8859_9</td>
+ *         <td>ISO-8859-9</td>
+ *         <td>ISO_8859-9:1989 iso-ir-148 ISO_8859-9 latin5 l5 csISOLatin5 8859_9 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>JISAutoDetect</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>JIS_C6626-1983</td>
+ *         <td>JIS_C6626-1983</td>
+ *         <td>x-JIS0208 JIS0208 csISO87JISX0208 x0208 JIS_X0208-1983 iso-ir-87 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>JIS_X0201</td>
+ *         <td>JIS_X0201</td>
+ *         <td>X0201 JIS0201 csHalfWidthKatakana </td>
+ *     </tr>
+ *     <tr>
+ *         <td>JIS_X0212-1990</td>
+ *         <td>JIS_X0212-1990</td>
+ *         <td>iso-ir-159 x0212 JIS0212 csISO159JISX02121990 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>KOI8_R</td>
+ *         <td>KOI8-R</td>
+ *         <td>csKOI8R koi8 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>MS874</td>
+ *         <td>windows-874</td>
+ *         <td>cp874 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>MS932</td>
+ *         <td>Windows-31J</td>
+ *         <td>windows-932 csWindows31J x-ms-cp932 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>MS949</td>
+ *         <td>windows-949</td>
+ *         <td>windows949 ms_949 x-windows-949 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>MS950</td>
+ *         <td>windows-950</td>
+ *         <td>x-windows-950 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>MS950_HKSCS</td>
+ *         <td></td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacArabic</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacCentralEurope</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacCroatian</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacCyrillic</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacDingbat</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacGreek</td>
+ *         <td>MacGreek</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacHebrew</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacIceland</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacRoman</td>
+ *         <td>MacRoman</td>
+ *         <td>Macintosh MAC csMacintosh </td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacRomania</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacSymbol</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacThai</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacTurkish</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>MacUkraine</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>SJIS</td>
+ *         <td>Shift_JIS</td>
+ *         <td>MS_Kanji csShiftJIS shift-jis x-sjis pck </td>
+ *     </tr>
+ *     <tr>
+ *         <td>TIS620</td>
+ *         <td>TIS-620</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>UTF-16</td>
+ *         <td>UTF-16</td>
+ *         <td>UTF_16 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>UTF8</td>
+ *         <td>UTF-8</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>UnicodeBig</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>UnicodeBigUnmarked</td>
+ *         <td>UTF-16BE</td>
+ *         <td>X-UTF-16BE UTF_16BE ISO-10646-UCS-2 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>UnicodeLittle</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>UnicodeLittleUnmarked</td>
+ *         <td>UTF-16LE</td>
+ *         <td>UTF_16LE X-UTF-16LE </td>
+ *     </tr>
+ *     <tr>
+ *         <td>x-Johab</td>
+ *         <td>johab</td>
+ *         <td>johab cp1361 ms1361 ksc5601-1992 ksc5601_1992 </td>
+ *     </tr>
+ *     <tr>
+ *         <td>x-iso-8859-11</td>
+ *         <td>?</td>
+ *         <td></td>
+ *     </tr>
+ * </table>
+ *
+ *
+ * @version $Id: CharsetUtil.java,v 1.1 2004/10/25 07:26:46 ntherning Exp $
+ */
+public class CharsetUtil {
+    private static Log log = LogFactory.getLog(CharsetUtil.class);
+
+    private static class Charset implements Comparable<Charset> {
+        private String canonical = null;
+        private String mime = null;
+        private String[] aliases = null;
+
+        private Charset(String canonical, String mime, String[] aliases) {
+            this.canonical = canonical;
+            this.mime = mime;
+            this.aliases = aliases;
+        }
+
+        public int compareTo(Charset c) {
+            return this.canonical.compareTo(c.canonical);
+        }
+    }
+
+    private static Charset[] JAVA_CHARSETS = {
+        new Charset("ISO8859_1", "ISO-8859-1",
+                    new String[] {"ISO_8859-1:1987", "iso-ir-100", "ISO_8859-1",
+                                  "latin1", "l1", "IBM819", "CP819",
+                                  "csISOLatin1", "8859_1", "819", "IBM-819",
+                                  "ISO8859-1", "ISO_8859_1"}),
+        new Charset("ISO8859_2", "ISO-8859-2",
+                    new String[] {"ISO_8859-2:1987", "iso-ir-101", "ISO_8859-2",
+                                  "latin2", "l2", "csISOLatin2", "8859_2",
+                                  "iso8859_2"}),
+        new Charset("ISO8859_3", "ISO-8859-3", new String[] {"ISO_8859-3:1988", "iso-ir-109", "ISO_8859-3", "latin3", "l3", "csISOLatin3", "8859_3"}),
+        new Charset("ISO8859_4", "ISO-8859-4",
+                    new String[] {"ISO_8859-4:1988", "iso-ir-110", "ISO_8859-4",
+                                  "latin4", "l4", "csISOLatin4", "8859_4"}),
+        new Charset("ISO8859_5", "ISO-8859-5",
+                    new String[] {"ISO_8859-5:1988", "iso-ir-144", "ISO_8859-5",
+                                  "cyrillic", "csISOLatinCyrillic", "8859_5"}),
+        new Charset("ISO8859_6", "ISO-8859-6", new String[] {"ISO_8859-6:1987", "iso-ir-127", "ISO_8859-6", "ECMA-114", "ASMO-708", "arabic", "csISOLatinArabic", "8859_6"}),
+        new Charset("ISO8859_7", "ISO-8859-7",
+                    new String[] {"ISO_8859-7:1987", "iso-ir-126", "ISO_8859-7",
+                                  "ELOT_928", "ECMA-118", "greek", "greek8",
+                                  "csISOLatinGreek", "8859_7", "sun_eu_greek"}),
+        new Charset("ISO8859_8", "ISO-8859-8", new String[] {"ISO_8859-8:1988", "iso-ir-138", "ISO_8859-8", "hebrew", "csISOLatinHebrew", "8859_8"}),
+        new Charset("ISO8859_9", "ISO-8859-9",
+                    new String[] {"ISO_8859-9:1989", "iso-ir-148", "ISO_8859-9",
+                                  "latin5", "l5", "csISOLatin5", "8859_9"}),
+
+        new Charset("ISO8859_13", "ISO-8859-13", new String[] {}),
+        new Charset("ISO8859_15", "ISO-8859-15",
+                    new String[] {"ISO_8859-15", "Latin-9", "8859_15",
+                                  "csISOlatin9", "IBM923", "cp923", "923", "L9",
+                                  "IBM-923", "ISO8859-15", "LATIN9", "LATIN0",
+                                  "csISOlatin0", "ISO8859_15_FDIS"}),
+        new Charset("KOI8_R", "KOI8-R", new String[] {"csKOI8R", "koi8"}),
+        new Charset("ASCII", "US-ASCII",
+                    new String[] {"ANSI_X3.4-1968", "iso-ir-6",
+                                  "ANSI_X3.4-1986", "ISO_646.irv:1991",
+                                  "ISO646-US", "us", "IBM367", "cp367",
+                                  "csASCII", "ascii7", "646", "iso_646.irv:1983"}),
+        new Charset("UTF8", "UTF-8", new String[] {}),
+        new Charset("UTF-16", "UTF-16", new String[] {"UTF_16"}),
+        new Charset("UnicodeBigUnmarked", "UTF-16BE", new String[] {"X-UTF-16BE", "UTF_16BE", "ISO-10646-UCS-2"}),
+        new Charset("UnicodeLittleUnmarked", "UTF-16LE", new String[] {"UTF_16LE", "X-UTF-16LE"}),
+        new Charset("Big5", "Big5", new String[] {"csBig5", "CN-Big5", "BIG-FIVE", "BIGFIVE"}),
+        new Charset("Big5_HKSCS", "Big5-HKSCS", new String[] {"big5hkscs"}),
+        new Charset("EUC_JP", "EUC-JP",
+                    new String[] {"csEUCPkdFmtJapanese",
+                              "Extended_UNIX_Code_Packed_Format_for_Japanese",
+                              "eucjis", "x-eucjp", "eucjp", "x-euc-jp"}),
+        new Charset("EUC_KR", "EUC-KR",
+                    new String[] {"csEUCKR", "ksc5601", "5601", "ksc5601_1987",
+                                  "ksc_5601", "ksc5601-1987", "ks_c_5601-1987",
+                                  "euckr"}),
+        new Charset("GB18030", "GB18030", new String[] {"gb18030-2000"}),
+        new Charset("EUC_CN", "GB2312", new String[] {"x-EUC-CN", "csGB2312", "euccn", "euc-cn", "gb2312-80", "gb2312-1980", "CN-GB", "CN-GB-ISOIR165"}),
+        new Charset("GBK", "windows-936", new String[] {"CP936", "MS936", "ms_936", "x-mswin-936"}),
+
+        new Charset("Cp037", "IBM037", new String[] {"ebcdic-cp-us", "ebcdic-cp-ca", "ebcdic-cp-wt", "ebcdic-cp-nl", "csIBM037"}),
+        new Charset("Cp273", "IBM273", new String[] {"csIBM273"}),
+        new Charset("Cp277", "IBM277", new String[] {"EBCDIC-CP-DK", "EBCDIC-CP-NO", "csIBM277"}),
+        new Charset("Cp278", "IBM278", new String[] {"CP278", "ebcdic-cp-fi", "ebcdic-cp-se", "csIBM278"}),
+        new Charset("Cp280", "IBM280", new String[] {"ebcdic-cp-it", "csIBM280"}),
+        new Charset("Cp284", "IBM284", new String[] {"ebcdic-cp-es", "csIBM284"}),
+        new Charset("Cp285", "IBM285", new String[] {"ebcdic-cp-gb", "csIBM285"}),
+        new Charset("Cp297", "IBM297", new String[] {"ebcdic-cp-fr", "csIBM297"}),
+        new Charset("Cp420", "IBM420", new String[] {"ebcdic-cp-ar1", "csIBM420"}),
+        new Charset("Cp424", "IBM424", new String[] {"ebcdic-cp-he", "csIBM424"}),
+        new Charset("Cp437", "IBM437", new String[] {"437", "csPC8CodePage437"}),
+        new Charset("Cp500", "IBM500", new String[] {"ebcdic-cp-be", "ebcdic-cp-ch", "csIBM500"}),
+        new Charset("Cp775", "IBM775", new String[] {"csPC775Baltic"}),
+        new Charset("Cp838", "IBM-Thai", new String[] {}),
+        new Charset("Cp850", "IBM850", new String[] {"850", "csPC850Multilingual"}),
+        new Charset("Cp852", "IBM852", new String[] {"852", "csPCp852"}),
+        new Charset("Cp855", "IBM855", new String[] {"855", "csIBM855"}),
+        new Charset("Cp857", "IBM857", new String[] {"857", "csIBM857"}),
+        new Charset("Cp858", "IBM00858",
+                new String[] {"CCSID00858", "CP00858",
+                              "PC-Multilingual-850+euro"}),
+        new Charset("Cp860", "IBM860", new String[] {"860", "csIBM860"}),
+        new Charset("Cp861", "IBM861", new String[] {"861", "cp-is", "csIBM861"}),
+        new Charset("Cp862", "IBM862", new String[] {"862", "csPC862LatinHebrew"}),
+        new Charset("Cp863", "IBM863", new String[] {"863", "csIBM863"}),
+        new Charset("Cp864", "IBM864", new String[] {"cp864", "csIBM864"}),
+        new Charset("Cp865", "IBM865", new String[] {"865", "csIBM865"}),
+        new Charset("Cp866", "IBM866", new String[] {"866", "csIBM866"}),
+        new Charset("Cp868", "IBM868", new String[] {"cp-ar", "csIBM868"}),
+        new Charset("Cp869", "IBM869", new String[] {"cp-gr", "csIBM869"}),
+        new Charset("Cp870", "IBM870", new String[] {"ebcdic-cp-roece", "ebcdic-cp-yu", "csIBM870"}),
+        new Charset("Cp871", "IBM871", new String[] {"ebcdic-cp-is", "csIBM871"}),
+        new Charset("Cp918", "IBM918", new String[] {"ebcdic-cp-ar2", "csIBM918"}),
+        new Charset("Cp1026", "IBM1026", new String[] {"csIBM1026"}),
+        new Charset("Cp1047", "IBM1047", new String[] {"IBM-1047"}),
+        new Charset("Cp1140", "IBM01140",
+                    new String[] {"CCSID01140", "CP01140",
+                                  "ebcdic-us-37+euro"}),
+        new Charset("Cp1141", "IBM01141",
+                    new String[] {"CCSID01141", "CP01141",
+                                  "ebcdic-de-273+euro"}),
+        new Charset("Cp1142", "IBM01142", new String[] {"CCSID01142", "CP01142", "ebcdic-dk-277+euro", "ebcdic-no-277+euro"}),
+        new Charset("Cp1143", "IBM01143", new String[] {"CCSID01143", "CP01143", "ebcdic-fi-278+euro", "ebcdic-se-278+euro"}),
+        new Charset("Cp1144", "IBM01144", new String[] {"CCSID01144", "CP01144", "ebcdic-it-280+euro"}),
+        new Charset("Cp1145", "IBM01145", new String[] {"CCSID01145", "CP01145", "ebcdic-es-284+euro"}),
+        new Charset("Cp1146", "IBM01146", new String[] {"CCSID01146", "CP01146", "ebcdic-gb-285+euro"}),
+        new Charset("Cp1147", "IBM01147", new String[] {"CCSID01147", "CP01147", "ebcdic-fr-297+euro"}),
+        new Charset("Cp1148", "IBM01148", new String[] {"CCSID01148", "CP01148", "ebcdic-international-500+euro"}),
+        new Charset("Cp1149", "IBM01149", new String[] {"CCSID01149", "CP01149", "ebcdic-is-871+euro"}),
+        new Charset("Cp1250", "windows-1250", new String[] {}),
+        new Charset("Cp1251", "windows-1251", new String[] {}),
+        new Charset("Cp1252", "windows-1252", new String[] {}),
+        new Charset("Cp1253", "windows-1253", new String[] {}),
+        new Charset("Cp1254", "windows-1254", new String[] {}),
+        new Charset("Cp1255", "windows-1255", new String[] {}),
+        new Charset("Cp1256", "windows-1256", new String[] {}),
+        new Charset("Cp1257", "windows-1257", new String[] {}),
+        new Charset("Cp1258", "windows-1258", new String[] {}),
+        new Charset("ISO2022CN", "ISO-2022-CN", new String[] {}),
+        new Charset("ISO2022JP", "ISO-2022-JP", new String[] {"csISO2022JP", "JIS", "jis_encoding", "csjisencoding"}),
+        new Charset("ISO2022KR", "ISO-2022-KR", new String[] {"csISO2022KR"}),
+        new Charset("JIS_X0201", "JIS_X0201", new String[] {"X0201", "JIS0201", "csHalfWidthKatakana"}),
+        new Charset("JIS_X0212-1990", "JIS_X0212-1990", new String[] {"iso-ir-159", "x0212", "JIS0212", "csISO159JISX02121990"}),
+        new Charset("JIS_C6626-1983", "JIS_C6626-1983", new String[] {"x-JIS0208", "JIS0208", "csISO87JISX0208", "x0208", "JIS_X0208-1983", "iso-ir-87"}),
+        new Charset("SJIS", "Shift_JIS", new String[] {"MS_Kanji", "csShiftJIS", "shift-jis", "x-sjis", "pck"}),
+        new Charset("TIS620", "TIS-620", new String[] {}),
+        new Charset("MS932", "Windows-31J", new String[] {"windows-932", "csWindows31J", "x-ms-cp932"}),
+        new Charset("EUC_TW", "EUC-TW", new String[] {"x-EUC-TW", "cns11643", "euctw"}),
+        new Charset("x-Johab", "johab", new String[] {"johab", "cp1361", "ms1361", "ksc5601-1992", "ksc5601_1992"}),
+        new Charset("MS950_HKSCS", "", new String[] {}),
+        new Charset("MS874", "windows-874", new String[] {"cp874"}),
+        new Charset("MS949", "windows-949", new String[] {"windows949", "ms_949", "x-windows-949"}),
+        new Charset("MS950", "windows-950", new String[] {"x-windows-950"}),
+
+        new Charset("Cp737", null, new String[] {}),
+        new Charset("Cp856", null, new String[] {}),
+        new Charset("Cp875", null, new String[] {}),
+        new Charset("Cp921", null, new String[] {}),
+        new Charset("Cp922", null, new String[] {}),
+        new Charset("Cp930", null, new String[] {}),
+        new Charset("Cp933", null, new String[] {}),
+        new Charset("Cp935", null, new String[] {}),
+        new Charset("Cp937", null, new String[] {}),
+        new Charset("Cp939", null, new String[] {}),
+        new Charset("Cp942", null, new String[] {}),
+        new Charset("Cp942C", null, new String[] {}),
+        new Charset("Cp943", null, new String[] {}),
+        new Charset("Cp943C", null, new String[] {}),
+        new Charset("Cp948", null, new String[] {}),
+        new Charset("Cp949", null, new String[] {}),
+        new Charset("Cp949C", null, new String[] {}),
+        new Charset("Cp950", null, new String[] {}),
+        new Charset("Cp964", null, new String[] {}),
+        new Charset("Cp970", null, new String[] {}),
+        new Charset("Cp1006", null, new String[] {}),
+        new Charset("Cp1025", null, new String[] {}),
+        new Charset("Cp1046", null, new String[] {}),
+        new Charset("Cp1097", null, new String[] {}),
+        new Charset("Cp1098", null, new String[] {}),
+        new Charset("Cp1112", null, new String[] {}),
+        new Charset("Cp1122", null, new String[] {}),
+        new Charset("Cp1123", null, new String[] {}),
+        new Charset("Cp1124", null, new String[] {}),
+        new Charset("Cp1381", null, new String[] {}),
+        new Charset("Cp1383", null, new String[] {}),
+        new Charset("Cp33722", null, new String[] {}),
+        new Charset("Big5_Solaris", null, new String[] {}),
+        new Charset("EUC_JP_LINUX", null, new String[] {}),
+        new Charset("EUC_JP_Solaris", null, new String[] {}),
+        new Charset("ISCII91", null, new String[] {"x-ISCII91", "iscii"}),
+        new Charset("ISO2022_CN_CNS", null, new String[] {}),
+        new Charset("ISO2022_CN_GB", null, new String[] {}),
+        new Charset("x-iso-8859-11", null, new String[] {}),
+        new Charset("JISAutoDetect", null, new String[] {}),
+        new Charset("MacArabic", null, new String[] {}),
+        new Charset("MacCentralEurope", null, new String[] {}),
+        new Charset("MacCroatian", null, new String[] {}),
+        new Charset("MacCyrillic", null, new String[] {}),
+        new Charset("MacDingbat", null, new String[] {}),
+        new Charset("MacGreek", "MacGreek", new String[] {}),
+        new Charset("MacHebrew", null, new String[] {}),
+        new Charset("MacIceland", null, new String[] {}),
+        new Charset("MacRoman", "MacRoman", new String[] {"Macintosh", "MAC", "csMacintosh"}),
+        new Charset("MacRomania", null, new String[] {}),
+        new Charset("MacSymbol", null, new String[] {}),
+        new Charset("MacThai", null, new String[] {}),
+        new Charset("MacTurkish", null, new String[] {}),
+        new Charset("MacUkraine", null, new String[] {}),
+        new Charset("UnicodeBig", null, new String[] {}),
+        new Charset("UnicodeLittle", null, new String[] {})
+    };
+
+    /**
+     * Contains the canonical names of character sets which can be used to
+     * decode bytes into Java chars.
+     */
+    private static TreeSet<String> decodingSupported = null;
+
+    /**
+     * Contains the canonical names of character sets which can be used to
+     * encode Java chars into bytes.
+     */
+    private static TreeSet<String> encodingSupported = null;
+
+    /**
+     * Maps character set names to Charset objects. All possible names of
+     * a charset will be mapped to the Charset.
+     */
+    private static HashMap<String, Charset> charsetMap = null;
+
+    static {
+        decodingSupported = new TreeSet<String>();
+        encodingSupported = new TreeSet<String>();
+        byte[] dummy = new byte[] {'d', 'u', 'm', 'm', 'y'};
+        for (int i = 0; i < JAVA_CHARSETS.length; i++) {
+            try {
+                String s = new String(dummy, JAVA_CHARSETS[i].canonical);
+                decodingSupported.add(JAVA_CHARSETS[i].canonical.toLowerCase(Locale.US));
+            } catch (UnsupportedOperationException e) {
+            } catch (UnsupportedEncodingException e) {
+            }
+            try {
+                "dummy".getBytes(JAVA_CHARSETS[i].canonical);
+                encodingSupported.add(JAVA_CHARSETS[i].canonical.toLowerCase(Locale.US));
+            } catch (UnsupportedOperationException e) {
+            } catch (UnsupportedEncodingException e) {
+            }
+        }
+
+        charsetMap = new HashMap<String, Charset>();
+        for (int i = 0; i < JAVA_CHARSETS.length; i++) {
+            Charset c = JAVA_CHARSETS[i];
+            charsetMap.put(c.canonical.toLowerCase(Locale.US), c);
+            if (c.mime != null) {
+                charsetMap.put(c.mime.toLowerCase(Locale.US), c);
+            }
+            if (c.aliases != null) {
+                for (int j = 0; j < c.aliases.length; j++) {
+                    charsetMap.put(c.aliases[j].toLowerCase(Locale.US), c);
+                }
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Character sets which support decoding: "
+                        + decodingSupported);
+            log.debug("Character sets which support encoding: "
+                        + encodingSupported);
+        }
+    }
+
+    /**
+     * ANDROID:  THE FOLLOWING SET OF STATIC STRINGS ARE COPIED FROM A NEWER VERSION OF MIME4J
+     */
+
+    /** carriage return - line feed sequence */
+    public static final String CRLF = "\r\n";
+
+    /** US-ASCII CR, carriage return (13) */
+    public static final int CR = '\r';
+
+    /** US-ASCII LF, line feed (10) */
+    public static final int LF = '\n';
+
+    /** US-ASCII SP, space (32) */
+    public static final int SP = ' ';
+
+    /** US-ASCII HT, horizontal-tab (9)*/
+    public static final int HT = '\t';
+
+    public static final java.nio.charset.Charset US_ASCII = java.nio.charset.Charset
+            .forName("US-ASCII");
+
+    public static final java.nio.charset.Charset ISO_8859_1 = java.nio.charset.Charset
+            .forName("ISO-8859-1");
+
+    public static final java.nio.charset.Charset UTF_8 = java.nio.charset.Charset
+            .forName("UTF-8");
+
+    /**
+     * Returns <code>true</code> if the specified character is a whitespace
+     * character (CR, LF, SP or HT).
+     *
+     * ANDROID:  COPIED FROM A NEWER VERSION OF MIME4J
+     *
+     * @param ch
+     *            character to test.
+     * @return <code>true</code> if the specified character is a whitespace
+     *         character, <code>false</code> otherwise.
+     */
+    public static boolean isWhitespace(char ch) {
+        return ch == SP || ch == HT || ch == CR || ch == LF;
+    }
+
+    /**
+     * Returns <code>true</code> if the specified string consists entirely of
+     * whitespace characters.
+     *
+     * ANDROID:  COPIED FROM A NEWER VERSION OF MIME4J
+     *
+     * @param s
+     *            string to test.
+     * @return <code>true</code> if the specified string consists entirely of
+     *         whitespace characters, <code>false</code> otherwise.
+     */
+    public static boolean isWhitespace(final String s) {
+        if (s == null) {
+            throw new IllegalArgumentException("String may not be null");
+        }
+        final int len = s.length();
+        for (int i = 0; i < len; i++) {
+            if (!isWhitespace(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Determines if the VM supports encoding (chars to bytes) the
+     * specified character set. NOTE: the given character set name may
+     * not be known to the VM even if this method returns <code>true</code>.
+     * Use {@link #toJavaCharset(String)} to get the canonical Java character
+     * set name.
+     *
+     * @param charsetName the characters set name.
+     * @return <code>true</code> if encoding is supported, <code>false</code>
+     *         otherwise.
+     */
+    public static boolean isEncodingSupported(String charsetName) {
+        return encodingSupported.contains(charsetName.toLowerCase(Locale.US));
+    }
+
+    /**
+     * Determines if the VM supports decoding (bytes to chars) the
+     * specified character set. NOTE: the given character set name may
+     * not be known to the VM even if this method returns <code>true</code>.
+     * Use {@link #toJavaCharset(String)} to get the canonical Java character
+     * set name.
+     *
+     * @param charsetName the characters set name.
+     * @return <code>true</code> if decoding is supported, <code>false</code>
+     *         otherwise.
+     */
+    public static boolean isDecodingSupported(String charsetName) {
+        return decodingSupported.contains(charsetName.toLowerCase(Locale.US));
+    }
+
+    /**
+     * Gets the preferred MIME character set name for the specified
+     * character set or <code>null</code> if not known.
+     *
+     * @param charsetName the character set name to look for.
+     * @return the MIME preferred name or <code>null</code> if not known.
+     */
+    public static String toMimeCharset(String charsetName) {
+        Charset c = charsetMap.get(charsetName.toLowerCase(Locale.US));
+        if (c != null) {
+            return c.mime;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the canonical Java character set name for the specified
+     * character set or <code>null</code> if not known. This should be
+     * called before doing any conversions using the Java API. NOTE:
+     * you must use {@link #isEncodingSupported(String)} or
+     * {@link #isDecodingSupported(String)} to make sure the returned
+     * Java character set is supported by the current VM.
+     *
+     * @param charsetName the character set name to look for.
+     * @return the canonical Java name or <code>null</code> if not known.
+     */
+    public static String toJavaCharset(String charsetName) {
+        Charset c = charsetMap.get(charsetName.toLowerCase(Locale.US));
+        if (c != null) {
+            return c.canonical;
+        }
+        return null;
+    }
+
+    public static java.nio.charset.Charset getCharset(String charsetName) {
+        String defaultCharset = "ISO-8859-1";
+
+        // Use the default chareset if given charset is null
+        if(charsetName == null) charsetName = defaultCharset;
+
+        try {
+            return java.nio.charset.Charset.forName(charsetName);
+        } catch (IllegalCharsetNameException e) {
+            log.info("Illegal charset " + charsetName + ", fallback to " +
+                    defaultCharset + ": " + e);
+            // Use default charset on exception
+            return java.nio.charset.Charset.forName(defaultCharset);
+        } catch (UnsupportedCharsetException ex) {
+            log.info("Unsupported charset " + charsetName + ", fallback to " +
+                    defaultCharset + ": " + ex);
+            // Use default charset on exception
+            return java.nio.charset.Charset.forName(defaultCharset);
+        }
+
+    }
+    /*
+     * Uncomment the code below and run the main method to regenerate the
+     * Javadoc table above when the known charsets change.
+     */
+
+    /*
+    private static String dumpHtmlTable() {
+        LinkedList l = new LinkedList(Arrays.asList(JAVA_CHARSETS));
+        Collections.sort(l);
+        StringBuffer sb = new StringBuffer();
+        sb.append(" * <table>\n");
+        sb.append(" *     <tr>\n");
+        sb.append(" *         <td>Canonical (Java) name</td>\n");
+        sb.append(" *         <td>MIME preferred</td>\n");
+        sb.append(" *         <td>Aliases</td>\n");
+        sb.append(" *     </tr>\n");
+
+        for (Iterator it = l.iterator(); it.hasNext();) {
+            Charset c = (Charset) it.next();
+            sb.append(" *     <tr>\n");
+            sb.append(" *         <td>" + c.canonical + "</td>\n");
+            sb.append(" *         <td>" + (c.mime == null ? "?" : c.mime)+ "</td>\n");
+            sb.append(" *         <td>");
+            for (int i = 0; c.aliases != null && i < c.aliases.length; i++) {
+                sb.append(c.aliases[i] + " ");
+            }
+            sb.append("</td>\n");
+            sb.append(" *     </tr>\n");
+        }
+        sb.append(" * </table>\n");
+        return sb.toString();
+    }
+
+    public static void main(String[] args) {
+        System.out.println(dumpHtmlTable());
+    }*/
+}
\ No newline at end of file