Merge "Thread list top-padding should disappear when scrolling" into ub-gmail-ur14-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6ae2b7c..a20f808 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -108,7 +108,7 @@
<activity
android:name=".photo.MailPhotoViewActivity"
android:label="@string/app_name"
- android:theme="@style/PhotoViewTheme" >
+ android:theme="@style/MailPhotoViewTheme" >
</activity>
<activity
android:name=".browse.EmlViewerActivity"
diff --git a/res/drawable-hdpi/ic_cv_loading_24dp.png b/res/drawable-hdpi/ic_cv_loading_24dp.png
new file mode 100644
index 0000000..077b8d0
--- /dev/null
+++ b/res/drawable-hdpi/ic_cv_loading_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_folder_parent_24dp.png b/res/drawable-hdpi/ic_folder_parent_24dp.png
new file mode 100644
index 0000000..68a6f4a
--- /dev/null
+++ b/res/drawable-hdpi/ic_folder_parent_24dp.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_revert_holo_dark.png b/res/drawable-hdpi/ic_menu_revert_holo_dark.png
deleted file mode 100644
index 6165a98..0000000
--- a/res/drawable-hdpi/ic_menu_revert_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_cv_loading_24dp.png b/res/drawable-mdpi/ic_cv_loading_24dp.png
new file mode 100644
index 0000000..d10e767
--- /dev/null
+++ b/res/drawable-mdpi/ic_cv_loading_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_folder_parent_24dp.png b/res/drawable-mdpi/ic_folder_parent_24dp.png
new file mode 100644
index 0000000..99d5076
--- /dev/null
+++ b/res/drawable-mdpi/ic_folder_parent_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_revert_holo_dark.png b/res/drawable-mdpi/ic_menu_revert_holo_dark.png
deleted file mode 100644
index 97ee13d..0000000
--- a/res/drawable-mdpi/ic_menu_revert_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_cv_loading_24dp.png b/res/drawable-xhdpi/ic_cv_loading_24dp.png
new file mode 100644
index 0000000..6f18799
--- /dev/null
+++ b/res/drawable-xhdpi/ic_cv_loading_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_folder_parent_24dp.png b/res/drawable-xhdpi/ic_folder_parent_24dp.png
new file mode 100644
index 0000000..da1655c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_folder_parent_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_revert_holo_dark.png b/res/drawable-xhdpi/ic_menu_revert_holo_dark.png
deleted file mode 100644
index 48ff5bc..0000000
--- a/res/drawable-xhdpi/ic_menu_revert_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_cv_loading_24dp.png b/res/drawable-xxhdpi/ic_cv_loading_24dp.png
new file mode 100644
index 0000000..38529dc
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_cv_loading_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_folder_parent_24dp.png b/res/drawable-xxhdpi/ic_folder_parent_24dp.png
new file mode 100644
index 0000000..06cc04a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_folder_parent_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_revert_holo_dark.png b/res/drawable-xxhdpi/ic_menu_revert_holo_dark.png
deleted file mode 100644
index b51df8a..0000000
--- a/res/drawable-xxhdpi/ic_menu_revert_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_cv_loading_24dp.png b/res/drawable-xxxhdpi/ic_cv_loading_24dp.png
new file mode 100644
index 0000000..5349075
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_cv_loading_24dp.png
Binary files differ
diff --git a/res/drawable/action_bar_shadow.xml b/res/drawable/action_bar_shadow.xml
deleted file mode 100644
index 3250117..0000000
--- a/res/drawable/action_bar_shadow.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 Google Inc.
- Licensed to 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.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <gradient
- android:angle="270"
- android:startColor="#3D000000"
- android:endColor="#00000000" />
-</shape>
diff --git a/res/layout-sw600dp/floating_actions.xml b/res/layout-sw600dp/floating_actions.xml
index 28bc3cf..ea5ae51 100644
--- a/res/layout-sw600dp/floating_actions.xml
+++ b/res/layout-sw600dp/floating_actions.xml
@@ -75,6 +75,7 @@
android:id="@+id/compose_button"
android:contentDescription="@string/compose"
android:focusable="false"
+ android:focusableInTouchMode="false"
style="@style/FloatingActionButtonStyle" />
</LinearLayout>
diff --git a/res/layout-v17/super_collapsed_progress.xml b/res/layout-v17/super_collapsed_progress.xml
deleted file mode 100644
index d6a599a..0000000
--- a/res/layout-v17/super_collapsed_progress.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 Google Inc.
- Licensed to 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.
--->
-<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/super_collapsed_progress"
- android:layout_width="40sp"
- android:layout_height="40sp"
- android:layout_gravity="center_vertical"
- android:layout_marginStart="@dimen/message_header_icon_additional_margin_start"
- android:background="@drawable/super_collapse_circle"
- android:indeterminate="true"
- android:indeterminateBehavior="repeat"
- android:visibility="gone"
- style="?android:attr/progressBarStyle" />
\ No newline at end of file
diff --git a/res/layout/floating_actions.xml b/res/layout/floating_actions.xml
index ed4519f..8df8612 100644
--- a/res/layout/floating_actions.xml
+++ b/res/layout/floating_actions.xml
@@ -25,6 +25,7 @@
android:id="@+id/compose_button"
android:contentDescription="@string/compose"
android:focusable="false"
+ android:focusableInTouchMode="false"
style="@style/FloatingActionButtonStyle" />
<com.android.mail.ui.ActionableToastBar
diff --git a/res/layout/one_pane_activity.xml b/res/layout/one_pane_activity.xml
index ee58ca3..c76beed 100644
--- a/res/layout/one_pane_activity.xml
+++ b/res/layout/one_pane_activity.xml
@@ -31,7 +31,9 @@
<FrameLayout
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:foregroundGravity="fill_horizontal|top"
+ android:foreground="?android:attr/windowContentOverlay" >
<FrameLayout
android:id="@+id/content_pane"
@@ -44,7 +46,6 @@
<include layout="@layout/search_suggestion_list" />
- <include layout="@layout/pre_l_actionbar_shadow" />
</FrameLayout>
</LinearLayout>
diff --git a/res/layout/pre_l_actionbar_shadow.xml b/res/layout/pre_l_actionbar_shadow.xml
deleted file mode 100644
index f0454b6..0000000
--- a/res/layout/pre_l_actionbar_shadow.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2012 Google Inc.
- Licensed to 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.
--->
-<View xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="7dp"
- android:background="@drawable/action_bar_shadow"
- style="@style/ActionBarShadow" />
diff --git a/res/layout/super_collapsed_block.xml b/res/layout/super_collapsed_block.xml
index 20968f5..a68f7b5 100644
--- a/res/layout/super_collapsed_block.xml
+++ b/res/layout/super_collapsed_block.xml
@@ -54,6 +54,15 @@
android:textSize="@dimen/super_collapsed_text_size"
style="@style/MessageHeaderIconStyle" />
- <include layout="@layout/super_collapsed_progress" />
+ <ImageView
+ android:id="@+id/super_collapsed_progress"
+ android:layout_width="40sp"
+ android:layout_height="40sp"
+ android:background="@drawable/super_collapse_circle"
+ android:contentDescription="@null"
+ android:scaleType="center"
+ android:src="@drawable/ic_cv_loading_24dp"
+ android:visibility="gone"
+ style="@style/MessageHeaderIconStyle" />
</com.android.mail.browse.SuperCollapsedBlock>
diff --git a/res/layout/super_collapsed_progress.xml b/res/layout/super_collapsed_progress.xml
deleted file mode 100644
index c7a0ec6..0000000
--- a/res/layout/super_collapsed_progress.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 Google Inc.
- Licensed to 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.
--->
-<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/super_collapsed_progress"
- android:layout_width="40sp"
- android:layout_height="40sp"
- android:layout_gravity="center_vertical"
- android:layout_marginLeft="@dimen/message_header_icon_additional_margin_start"
- android:background="@drawable/super_collapse_circle"
- android:indeterminate="true"
- android:indeterminateBehavior="repeat"
- android:visibility="gone"
- style="?android:attr/progressBarStyle" />
\ No newline at end of file
diff --git a/res/layout/swipe_leavebehind_body.xml b/res/layout/swipe_leavebehind_body.xml
index cfe8858..106c7e8 100644
--- a/res/layout/swipe_leavebehind_body.xml
+++ b/res/layout/swipe_leavebehind_body.xml
@@ -18,47 +18,25 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:clickable="true"
android:orientation="horizontal">
+
<TextView
- android:id="@+id/undo_descriptionview"
- android:layout_width="0dip"
+ android:id="@+id/undo_description_text"
+ android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
- android:textColor="@android:color/white"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:clickable="true"
android:gravity="center_vertical"
+ android:textColor="@android:color/white"
+ android:textSize="16sp"
style="@style/UndoDescriptionStyle" />
- <View
- android:id="@+id/undo_separator"
- android:layout_width="1dip"
- android:layout_height="match_parent"
- android:background="@android:color/white"
- android:layout_marginTop="16dp"
- android:layout_marginBottom="16dp" />
-
- <ImageView
- android:id="@+id/undo_icon"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:src="@drawable/ic_menu_revert_holo_dark"
- android:background="?android:attr/selectableItemBackground"
- android:duplicateParentState="true"
- style="@style/UndoIconStyle" />
-
<TextView
android:id="@+id/undo_text"
- style="@style/UndoTextStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:text="@string/undo"
- android:textAllCaps="true"
android:gravity="center_vertical"
- android:textColor="@android:color/white"
- android:background="?android:attr/selectableItemBackground"
- android:duplicateParentState="true"/>
+ style="@style/UndoTextStyle" />
+
</LinearLayout>
diff --git a/res/layout/two_pane_activity.xml b/res/layout/two_pane_activity.xml
index c616643..ca11599 100644
--- a/res/layout/two_pane_activity.xml
+++ b/res/layout/two_pane_activity.xml
@@ -32,6 +32,8 @@
android:id="@+id/two_pane_activity"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:foregroundGravity="fill_horizontal|top"
+ android:foreground="?android:attr/windowContentOverlay"
android:background="@color/tablet_background_gray">
<FrameLayout
@@ -77,7 +79,6 @@
<include layout="@layout/search_suggestion_list" />
- <include layout="@layout/pre_l_actionbar_shadow" />
</FrameLayout>
</LinearLayout>
diff --git a/res/layout/undo_notification.xml b/res/layout/undo_notification.xml
index 9358da8..0a48e9f 100644
--- a/res/layout/undo_notification.xml
+++ b/res/layout/undo_notification.xml
@@ -26,58 +26,30 @@
internal:layout_minHeight="64dp"
android:background="@drawable/notification_bg" >
- <ImageView
- android:layout_width="@android:dimen/notification_large_icon_width"
- android:layout_height="@android:dimen/notification_large_icon_height"
- android:background="@color/notification_template_icon_low_bg"
- android:scaleType="center"
- android:src="@drawable/ic_notification_mail_24dp" />
-
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_vertical"
android:gravity="top"
android:minHeight="@android:dimen/notification_large_icon_height"
- android:orientation="horizontal"
- android:paddingBottom="2dp"
- android:paddingTop="2dp"
- style="@style/UndoNotificationStyle" >
+ android:orientation="horizontal" >
<TextView
android:id="@+id/description_text"
android:layout_width="0dp"
android:layout_height="match_parent"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="8dp"
android:layout_weight="1"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal"
- android:gravity="center_vertical" />
-
- <ImageView
- android:layout_width="1dip"
- android:layout_height="match_parent"
- android:layout_marginBottom="10dip"
- android:layout_marginRight="12dip"
- android:layout_marginTop="10dip"
- android:src="#aaaaaa" />
-
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:layout_marginRight="4dip"
- android:src="@drawable/ic_menu_revert_holo_dark" />
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:textColor="@android:color/white"
+ android:textSize="16sp"
+ style="@style/UndoDescriptionStyle" />
<TextView
- style="@style/UndoTextStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginLeft="4dip"
android:gravity="center_vertical"
- android:text="@string/undo"
- android:textAllCaps="true" />
+ style="@style/UndoTextStyle" />
</LinearLayout>
</FrameLayout>
diff --git a/res/values-ldrtl/styles-ldrtl.xml b/res/values-ldrtl/styles-ldrtl.xml
index 19b86cc..6141383 100644
--- a/res/values-ldrtl/styles-ldrtl.xml
+++ b/res/values-ldrtl/styles-ldrtl.xml
@@ -45,10 +45,6 @@
<item name="android:paddingEnd">@dimen/send_mail_as_padding</item>
</style>
- <style name="UndoTextStyle" parent="AbstractUndoTextStyle">
- <item name="android:paddingEnd">@dimen/undo_text_padding</item>
- </style>
-
<style name="MessageHeaderIconStyle">
<item name="android:layout_marginStart">@dimen/message_header_icon_additional_margin_start</item>
</style>
@@ -105,10 +101,6 @@
<item name="android:layout_marginStart">@dimen/attachment_icon_padding</item>
</style>
- <style name="UndoNotificationStyle">
- <item name="android:layout_marginStart">@android:dimen/notification_large_icon_width</item>
- </style>
-
<style name="UnreadCountWithMarginEndStyle" parent="UnreadCountRelativeLayout">
<item name="android:layout_marginEnd">@dimen/folder_list_item_end_margin</item>
<item name="android:layout_alignParentEnd">true</item>
@@ -372,11 +364,6 @@
<item name="android:paddingStart">@dimen/undo_description_padding</item>
</style>
- <style name="UndoIconStyle">
- <item name="android:paddingStart">@dimen/undo_icon_padding_start</item>
- <item name="android:paddingEnd">@dimen/undo_icon_padding_end</item>
- </style>
-
<style name="AccountItemNameStyle">
<item name="android:paddingStart">@dimen/account_item_name_start_padding</item>
<item name="android:paddingEnd">@dimen/account_item_name_end_padding</item>
diff --git a/res/values-sw720dp/constants.xml b/res/values-sw720dp/constants.xml
index 3653baf..bd90d8b 100644
--- a/res/values-sw720dp/constants.xml
+++ b/res/values-sw720dp/constants.xml
@@ -25,6 +25,4 @@
<!-- Whether to show single or 2 pane search results -->
<bool name="show_two_pane_search_results">false</bool>
- <!-- Whether or not to use a nice transition when showing the folder list fragment; used in 2-pane layouts -->
- <integer name="use_folder_list_fragment_transition">0</integer>
</resources>
diff --git a/res/values/animation_constants.xml b/res/values/animation_constants.xml
index 65bb190..532d144 100644
--- a/res/values/animation_constants.xml
+++ b/res/values/animation_constants.xml
@@ -21,7 +21,7 @@
<integer name="dialog_animationDefaultDur">220</integer>
<integer name="dialog_animationShortDur">150</integer>
<integer name="shrink_animation_duration">200</integer>
- <integer name="slide_animation_duration">320</integer>
+ <integer name="slide_animation_duration">200</integer>
<integer name="fade_in_animation_duration">300</integer>
<!-- Swipe constants -->
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 64b9abe..ccfbb09 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -20,7 +20,6 @@
<color name="text_color_black">#212121</color>
<color name="accent_blue">#00bfd8</color>
<color name="text_color_blue">@color/accent_blue</color>
- <color name="text_color_dark_blue">#4285f4</color>
<color name="text_color_draft_red">#da4336</color>
<color name="gray_text_color">#777777</color>
<color name="dark_gray_text_color">#58585b</color>
@@ -35,7 +34,7 @@
<color name="senders_text_color">@color/text_color_black</color>
<color name="subject_text_color">@color/text_color_black</color>
<color name="snippet_text_color">@color/text_color_grey</color>
- <color name="date_text_color_unread">@color/text_color_dark_blue</color>
+ <color name="date_text_color_unread">#4285f4</color>
<color name="date_text_color_read">@color/text_color_grey</color>
<color name="message_info_text_color">@color/gray_text_color</color>
<color name="ap_background_color">#fff1f1f2</color>
@@ -43,7 +42,7 @@
<color name="ap_overflow_text_color">#ff4f4c4c</color>
<color name="attachment_bg_color">#ffdddddd</color>
- <color name="swiped_bg_color">#ff999999</color>
+ <color name="swiped_bg_color">@color/text_color_grey</color>
<color name="holo_light_background_color">#ffe8e8e8</color>
<!-- Keep the following two colors in sync with LabelColorUtils.DEFAULT_COLORS (UnifiedGmail) -->
<color name="default_folder_background_color">#dddddd</color>
@@ -98,8 +97,7 @@
<color name="low_spam_color">#333333</color>
<color name="low_spam_warning_background_color">#FFF1A8</color>
- <color name="notification_template_icon_low_bg">#0cffffff</color>
- <color name="notification_icon_gmail_red">#da4336</color>
+ <color name="notification_icon_color">#da4336</color>
<!-- Search colors -->
<color name="search_query_hint_text">@color/light_gray</color>
diff --git a/res/values/constants.xml b/res/values/constants.xml
index 8b4817b..e67917d 100644
--- a/res/values/constants.xml
+++ b/res/values/constants.xml
@@ -83,9 +83,6 @@
<!-- The width or height of the preview will not exceed this -->
<integer name="attachment_preview_max_size">256</integer>
- <!-- Whether or not to use a nice transition when showing the folder list fragment; used in 2-pane layouts -->
- <integer name="use_folder_list_fragment_transition">1</integer>
-
<!-- The timeout, in milliseconds, before the "Undo" notification is removed. -->
<integer name="undo_notification_timeout">6000</integer>
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index 4fb8f35..8f7909a 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -119,7 +119,6 @@
<dimen name="tile_divider_width">1dp</dimen>
<dimen name="checked_text_padding">16dip</dimen>
<dimen name="send_mail_as_padding">8dip</dimen>
- <dimen name="undo_text_padding">16dip</dimen>
<dimen name="dismiss_separator_padding">16dip</dimen>
<dimen name="teaser_text_padding">16dp</dimen>
<dimen name="attachment_padding_start">16dp</dimen>
@@ -197,9 +196,8 @@
<dimen name="avatar_border_width">.5dp</dimen>
<dimen name="progress_bar_height">12dp</dimen>
- <dimen name="undo_description_padding">16dip</dimen>
- <dimen name="undo_icon_padding_start">12dip</dimen>
- <dimen name="undo_icon_padding_end">8dip</dimen>
+ <dimen name="undo_text_padding">24dp</dimen>
+ <dimen name="undo_description_padding">24dp</dimen>
<!-- The width of the wearable image background. -->
<dimen name="wearable_background_width">320dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b696297..d9986e5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -287,9 +287,6 @@
<string name="contextmenu_feedback">Send feedback</string>
<!-- Browse list item strings -->
- <!-- Text indicating how many messages are selected in the top action bar
- [CHAR LIMIT=40] -->
- <string name="num_selected"><xliff:g id="number" example="7">%d</xliff:g></string>
<!-- Formatting string for the content description field of a conversation list item when device is in accessibility mode. [CHAR LIMIT=250] -->
<string name="content_description"><xliff:g id="toHeader">%1$s</xliff:g><xliff:g id="participant">%2$s</xliff:g> about <xliff:g id="subject">%3$s</xliff:g>, <xliff:g id="snippet">%4$s</xliff:g> on <xliff:g id="date">%5$s</xliff:g>, <xliff:g id="readstate">%6$s</xliff:g></string>
<!-- Formatting string for the content description field of a conversation list item when device is in accessibility mode and the message was received today. [CHAR LIMI=250] -->
@@ -580,9 +577,6 @@
<item quantity="other"><xliff:g id="count" example="4">%1$d</xliff:g> unread</item>
</plurals>
- <!-- Displayed in the actionbar when unread count > 99. [CHAR LIMIT=30] -->
- <string name="actionbar_large_unread_count"><xliff:g id="count">%1$d</xliff:g>+ unread</string>
-
<!-- Displayed at the end of the conversation list in the widget. Tapping on this will open the default Inbox. [CHAR LIMIT=35] -->
<string name="view_more_conversations">View more conversations</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 71ea20b..3a75b79 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -244,14 +244,14 @@
</style>
<!-- Undo bar styles -->
- <style name="AbstractUndoTextStyle">
- <item name="android:textSize">12sp</item>
- <item name="android:textColor">#aaaaaa</item>
- <item name="android:textStyle">bold</item>
- </style>
-
- <style name="UndoTextStyle" parent="AbstractUndoTextStyle">
+ <style name="UndoTextStyle">
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="android:paddingLeft">@dimen/undo_text_padding</item>
<item name="android:paddingRight">@dimen/undo_text_padding</item>
+ <item name="android:text">@string/undo</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textColor">#feb500</item>
+ <item name="android:textSize">16sp</item>
</style>
<!-- End undo bar styles -->
@@ -649,10 +649,6 @@
<item name="android:layout_marginLeft">@dimen/attachment_icon_padding</item>
</style>
- <style name="UndoNotificationStyle">
- <item name="android:layout_marginLeft">@android:dimen/notification_large_icon_width</item>
- </style>
-
<style name="UnreadCountWithMarginEndStyle" parent="UnreadCountRelativeLayout">
<item name="android:layout_marginRight">@dimen/folder_list_item_end_margin</item>
<item name="android:layout_alignParentRight">true</item>
@@ -956,11 +952,6 @@
<item name="android:paddingLeft">@dimen/undo_description_padding</item>
</style>
- <style name="UndoIconStyle">
- <item name="android:paddingLeft">@dimen/undo_icon_padding_start</item>
- <item name="android:paddingRight">@dimen/undo_icon_padding_end</item>
- </style>
-
<style name="AccountItemNameStyle">
<item name="android:paddingLeft">@dimen/account_item_name_start_padding</item>
<item name="android:paddingRight">@dimen/account_item_name_end_padding</item>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 6da39b1..5ba608d 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -60,4 +60,8 @@
<item name="android:windowAnimationStyle">@android:style/Animation</item>
</style>
-</resources>
\ No newline at end of file
+ <style name="MailPhotoViewTheme" parent="@style/PhotoViewTheme">
+ <item name="colorPrimary">@color/actionbar_color</item>
+ </style>
+
+</resources>
diff --git a/src/com/android/mail/MailIntentService.java b/src/com/android/mail/MailIntentService.java
index 5996a96..6c4c3cf 100644
--- a/src/com/android/mail/MailIntentService.java
+++ b/src/com/android/mail/MailIntentService.java
@@ -120,6 +120,7 @@
public static void broadcastBackupDataChanged(final Context context) {
final Intent intent = new Intent(ACTION_BACKUP_DATA_CHANGED);
+ intent.setPackage(context.getPackageName());
context.startService(intent);
}
diff --git a/src/com/android/mail/browse/ConversationPagerAdapter.java b/src/com/android/mail/browse/ConversationPagerAdapter.java
index 70620b4..a6c794b 100644
--- a/src/com/android/mail/browse/ConversationPagerAdapter.java
+++ b/src/com/android/mail/browse/ConversationPagerAdapter.java
@@ -19,7 +19,7 @@
import android.app.Fragment;
import android.app.FragmentManager;
-import android.content.res.Resources;
+import android.content.Context;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
@@ -27,6 +27,7 @@
import android.support.v4.view.ViewPager;
import android.view.ViewGroup;
+import com.android.mail.preferences.MailPrefs;
import com.android.mail.providers.Account;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.Folder;
@@ -37,6 +38,7 @@
import com.android.mail.ui.ConversationViewFragment;
import com.android.mail.ui.SecureConversationViewFragment;
import com.android.mail.utils.FragmentStatePagerAdapter2;
+import com.android.mail.utils.HtmlSanitizer;
import com.android.mail.utils.LogUtils;
public class ConversationPagerAdapter extends FragmentStatePagerAdapter2
@@ -67,10 +69,8 @@
* True iff we are in the process of handling a dataset change.
*/
private boolean mInDataSetChange = false;
- /**
- * Need to keep this around to look up pager title strings.
- */
- private Resources mResources;
+
+ private Context mContext;
/**
* This isn't great to create a circular dependency, but our usage of {@link #getPageTitle(int)}
* requires knowing which page is the currently visible to dynamically name offscreen pages
@@ -81,7 +81,16 @@
* minimize dangling references.
*/
private ViewPager mPager;
- private boolean mSanitizedHtml;
+
+ /**
+ * <tt>true</tt> indicates the server has already sanitized all HTML email from this account.
+ */
+ private boolean mServerSanitizedHtml;
+
+ /**
+ * <tt>true</tt> indicates the client is permitted to sanitize all HTML email for this account.
+ */
+ private boolean mClientSanitizedHtml;
private boolean mStopListeningMode = false;
@@ -104,16 +113,18 @@
private static final String BUNDLE_DETACHED_MODE =
ConversationPagerAdapter.class.getName() + "-detachedmode";
- public ConversationPagerAdapter(Resources res, FragmentManager fm, Account account,
+ public ConversationPagerAdapter(Context context, FragmentManager fm, Account account,
Folder folder, Conversation initialConversation) {
super(fm, false /* enableSavedStates */);
- mResources = res;
+ mContext = context;
mCommonFragmentArgs = AbstractConversationViewFragment.makeBasicArgs(account);
mInitialConversation = initialConversation;
mAccount = account;
mFolder = folder;
- mSanitizedHtml = mAccount.supportsCapability
- (UIProvider.AccountCapabilities.SANITIZED_HTML);
+ mServerSanitizedHtml =
+ mAccount.supportsCapability(UIProvider.AccountCapabilities.SERVER_SANITIZED_HTML);
+ mClientSanitizedHtml =
+ mAccount.supportsCapability(UIProvider.AccountCapabilities.CLIENT_SANITIZED_HTML);
}
public boolean matches(Account account, Folder folder) {
@@ -194,11 +205,23 @@
}
private AbstractConversationViewFragment getConversationViewFragment(Conversation c) {
- if (mSanitizedHtml) {
+ // if Html email bodies are already sanitized by the mail server, scripting can be enabled
+ if (mServerSanitizedHtml) {
return ConversationViewFragment.newInstance(mCommonFragmentArgs, c);
- } else {
- return SecureConversationViewFragment.newInstance(mCommonFragmentArgs, c);
}
+
+ // if this client is permitted to sanitize emails for this account, attempt to do so
+ if (mClientSanitizedHtml) {
+ // if the version of the Html Sanitizer meets or exceeds the required version, the
+ // results of the sanitizer can be trusted and scripting can be enabled
+ final MailPrefs mailPrefs = MailPrefs.get(mContext);
+ if (HtmlSanitizer.VERSION >= mailPrefs.getRequiredSanitizerVersionNumber()) {
+ return ConversationViewFragment.newInstance(mCommonFragmentArgs, c);
+ }
+ }
+
+ // otherwise we do not enable scripting
+ return SecureConversationViewFragment.newInstance(mCommonFragmentArgs, c);
}
@Override
diff --git a/src/com/android/mail/browse/ConversationPagerController.java b/src/com/android/mail/browse/ConversationPagerController.java
index 347b4ae..87190f5 100644
--- a/src/com/android/mail/browse/ConversationPagerController.java
+++ b/src/com/android/mail/browse/ConversationPagerController.java
@@ -117,7 +117,7 @@
mPager.setVisibility(View.VISIBLE);
}
- mPagerAdapter = new ConversationPagerAdapter(mPager.getResources(), mFragmentManager,
+ mPagerAdapter = new ConversationPagerAdapter(mPager.getContext(), mFragmentManager,
account, folder, initialConversation);
mPagerAdapter.setSingletonMode(ENABLE_SINGLETON_INITIAL_LOAD);
mPagerAdapter.setActivityController(mActivityController);
diff --git a/src/com/android/mail/browse/ConversationViewHeader.java b/src/com/android/mail/browse/ConversationViewHeader.java
index 68a1604..6475b7a 100644
--- a/src/com/android/mail/browse/ConversationViewHeader.java
+++ b/src/com/android/mail/browse/ConversationViewHeader.java
@@ -17,7 +17,15 @@
package com.android.mail.browse;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
@@ -39,7 +47,8 @@
* there is enough room to fit both without wrapping. If they overlap, it
* adjusts the layout to position the folders below the subject.
*/
-public class ConversationViewHeader extends LinearLayout implements OnClickListener {
+public class ConversationViewHeader extends LinearLayout implements OnClickListener,
+ View.OnLongClickListener {
public interface ConversationViewHeaderCallbacks {
/**
@@ -53,6 +62,8 @@
* @param newHeight the new height in px
*/
void onConversationViewHeaderHeightChange(int newHeight);
+
+ Activity getActivity();
}
private static final String LOG_TAG = LogTag.getLogTag();
@@ -82,6 +93,7 @@
mSubjectAndFolderView =
(SubjectAndFolderView) findViewById(R.id.subject_and_folder_view);
+ mSubjectAndFolderView.setOnLongClickListener(this);
mStarView = (StarView) findViewById(R.id.conversation_header_star);
mStarView.setOnClickListener(this);
}
@@ -154,6 +166,52 @@
mConversationUpdater.updateConversation(Conversation.listOf(mConversation),
UIProvider.ConversationColumns.STARRED, mConversation.starred);
}
+ }
+ @Override
+ public boolean onLongClick(View v) {
+ final DialogFragment frag =
+ CopySubjectDialog.newInstance(mSubjectAndFolderView.getSubject());
+ frag.show(mCallbacks.getActivity().getFragmentManager(), CopySubjectDialog.TAG);
+ return true;
+ }
+
+ public static class CopySubjectDialog extends DialogFragment
+ implements DialogInterface.OnClickListener {
+
+ public static final String TAG = "copy-subject-dialog";
+
+ private static final String ARG_SUBJECT = "subject";
+
+ private String mSubject;
+
+ public static CopySubjectDialog newInstance(String subject) {
+ final CopySubjectDialog frag = new CopySubjectDialog();
+ final Bundle args = new Bundle(1);
+ args.putString(ARG_SUBJECT, subject);
+ frag.setArguments(args);
+ return frag;
+ }
+
+ public CopySubjectDialog() {}
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ mSubject = getArguments().getString(ARG_SUBJECT);
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(mSubject)
+ .setPositiveButton(R.string.contextmenu_copy, this)
+ .setNegativeButton(R.string.cancel, this)
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ final ClipboardManager clipboard = (ClipboardManager)
+ getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(ClipData.newPlainText(null, mSubject));
+ }
+ }
}
}
diff --git a/src/com/android/mail/browse/SelectedConversationsActionMenu.java b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
index fd0933f..e3b9581 100644
--- a/src/com/android/mail/browse/SelectedConversationsActionMenu.java
+++ b/src/com/android/mail/browse/SelectedConversationsActionMenu.java
@@ -349,6 +349,7 @@
final MenuInflater inflater = mActivity.getMenuInflater();
inflater.inflate(R.menu.conversation_list_selection_actions_menu, menu);
mActionMode = mode;
+ updateCount();
return true;
}
@@ -520,6 +521,7 @@
if (set.isEmpty()) {
return;
}
+ updateCount();
if (mFolder.isType(FolderType.OUTBOX) && mDiscardOutboxMenuItem != null) {
mDiscardOutboxMenuItem.setEnabled(shouldEnableDiscardOutbox(set.values()));
@@ -527,6 +529,15 @@
}
/**
+ * Updates the visible count of how many conversations are selected.
+ */
+ private void updateCount() {
+ if (mActionMode != null) {
+ mActionMode.setTitle(Integer.toString(mCheckedSet.size()));
+ }
+ }
+
+ /**
* Activates and shows this menu (essentially starting an {@link ActionMode}) if the selected
* set is non-empty.
*/
diff --git a/src/com/android/mail/browse/SubjectAndFolderView.java b/src/com/android/mail/browse/SubjectAndFolderView.java
index 742d6af..fcfd5a6 100644
--- a/src/com/android/mail/browse/SubjectAndFolderView.java
+++ b/src/com/android/mail/browse/SubjectAndFolderView.java
@@ -199,6 +199,10 @@
return mBidiFormatter;
}
+ public String getSubject() {
+ return mSubject;
+ }
+
private static class ConversationFolderDisplayer extends FolderDisplayer {
private final FolderSpan.FolderSpanDimensions mDims;
diff --git a/src/com/android/mail/compose/ComposeActivity.java b/src/com/android/mail/compose/ComposeActivity.java
index 00e0d45..82116c7 100644
--- a/src/com/android/mail/compose/ComposeActivity.java
+++ b/src/com/android/mail/compose/ComposeActivity.java
@@ -267,6 +267,7 @@
private static final Handler SEND_SAVE_TASK_HANDLER;
// String representing the uri of the data directory (used for attachment uri checking).
private static final String DATA_DIRECTORY_ROOT;
+ private static final String ALTERNATE_DATA_DIRECTORY_ROOT;
// Static initializations
static {
@@ -275,6 +276,7 @@
SEND_SAVE_TASK_HANDLER = new Handler(handlerThread.getLooper());
DATA_DIRECTORY_ROOT = Environment.getDataDirectory().toString();
+ ALTERNATE_DATA_DIRECTORY_ROOT = DATA_DIRECTORY_ROOT + DATA_DIRECTORY_ROOT;
}
private ScrollView mScrollView;
@@ -1871,10 +1873,34 @@
if (uri != null) {
if ("file".equals(uri.getScheme())) {
final File f = new File(uri.getPath());
- // We should not be attaching any files from the data directory.
- if (f.getCanonicalPath().startsWith(DATA_DIRECTORY_ROOT)) {
- showErrorToast(getString(R.string.attachment_permission_denied));
- continue;
+ // We should not be attaching any files from the data directory UNLESS
+ // the data directory is part of the calling process.
+ final String filePath = f.getCanonicalPath();
+ if (filePath.startsWith(DATA_DIRECTORY_ROOT)) {
+ final String callingPackage = getCallingPackage();
+ if (callingPackage == null) {
+ showErrorToast(getString(R.string.attachment_permission_denied));
+ continue;
+ }
+
+ // So it looks like the data directory are usually /data/data, but
+ // DATA_DIRECTORY_ROOT is only /data.. so let's check for both
+ final String pathWithoutRoot;
+ // We add 1 to the length for the additional / before the package name.
+ if (filePath.startsWith(ALTERNATE_DATA_DIRECTORY_ROOT)) {
+ pathWithoutRoot = filePath.substring(
+ ALTERNATE_DATA_DIRECTORY_ROOT.length() + 1);
+ } else {
+ pathWithoutRoot = filePath.substring(
+ DATA_DIRECTORY_ROOT.length() + 1);
+ }
+
+ // If we are trying to access a data package that's not part of the
+ // calling package, show error toast and ignore this attachment.
+ if (!pathWithoutRoot.startsWith(callingPackage)) {
+ showErrorToast(getString(R.string.attachment_permission_denied));
+ continue;
+ }
}
}
if (!handleSpecialAttachmentUri(uri)) {
@@ -1889,6 +1915,7 @@
LogUtils.e(LOG_TAG, e, "Error adding attachment");
showAttachmentTooBigToast(e.getErrorRes());
} catch (IOException | SecurityException e) {
+ LogUtils.e(LOG_TAG, e, "Error adding attachment");
showErrorToast(getString(R.string.attachment_permission_denied));
}
}
diff --git a/src/com/android/mail/lib/base/ByteArrays.java b/src/com/android/mail/lib/base/ByteArrays.java
deleted file mode 100644
index da811f2..0000000
--- a/src/com/android/mail/lib/base/ByteArrays.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2008 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.mail.lib.base;
-
-/**
- * Static utility methods pertaining especially to byte arrays. Note that I/O-related functionality
- * belongs in the {@code com.google.common.io} package.
- *
- * @author Chris Nokleberg
- * @author Hiroshi Yamauchi
- */
-public final class ByteArrays {
- private ByteArrays() {}
-
- private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
-
- /**
- * Returns the byte array formatted as a lowercase hexadecimal string. The string will be
- * {@code 2 * bytes.length} characters long.
- */
- public static String toHexString(byte[] bytes) {
- StringBuilder sb = new StringBuilder(2 * bytes.length);
- for (byte b : bytes) {
- sb.append(HEX_DIGITS[(b >> 4) & 0xf]).append(HEX_DIGITS[b & 0xf]);
- }
- return sb.toString();
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/CharEscaper.java b/src/com/android/mail/lib/base/CharEscaper.java
deleted file mode 100644
index 63ee395..0000000
--- a/src/com/android/mail/lib/base/CharEscaper.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2006 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.mail.lib.base;
-
-import static com.android.mail.lib.base.Preconditions.checkNotNull;
-
-import java.io.IOException;
-
-/**
- * An object that converts literal text into a format safe for inclusion in a particular context
- * (such as an XML document). Typically (but not always), the inverse process of "unescaping" the
- * text is performed automatically by the relevant parser.
- *
- * <p>For example, an XML escaper would convert the literal string {@code "Foo<Bar>"} into {@code
- * "Foo<Bar>"} to prevent {@code "<Bar>"} from being confused with an XML tag. When the
- * resulting XML document is parsed, the parser API will return this text as the original literal
- * string {@code "Foo<Bar>"}.
- *
- * <p>A {@code CharEscaper} instance is required to be stateless, and safe when used concurrently by
- * multiple threads.
- *
- * <p>Several popular escapers are defined as constants in the class {@link CharEscapers}. To create
- * your own escapers, use {@link CharEscaperBuilder}, or extend this class and implement the {@link
- * #escape(char)} method.
- *
- * @author sven@google.com (Sven Mawson)
- */
-public abstract class CharEscaper extends Escaper {
- /**
- * Returns the escaped form of a given literal string.
- *
- * @param string the literal string to be escaped
- * @return the escaped form of {@code string}
- * @throws NullPointerException if {@code string} is null
- */
- @Override public String escape(String string) {
- checkNotNull(string);
- // Inlineable fast-path loop which hands off to escapeSlow() only if needed
- int length = string.length();
- for (int index = 0; index < length; index++) {
- if (escape(string.charAt(index)) != null) {
- return escapeSlow(string, index);
- }
- }
- return string;
- }
-
- /**
- * Returns an {@code Appendable} instance which automatically escapes all text appended to it
- * before passing the resulting text to an underlying {@code Appendable}.
- *
- * <p>The methods of the returned object will propagate any exceptions thrown by the underlying
- * {@code Appendable}, and will throw {@link NullPointerException} if asked to append {@code
- * null}, but do not otherwise throw any exceptions.
- *
- * <p>The escaping behavior is identical to that of {@link #escape(String)}, so the following code
- * is always equivalent to {@code escaper.escape(string)}: <pre> {@code
- *
- * StringBuilder sb = new StringBuilder();
- * escaper.escape(sb).append(string);
- * return sb.toString();}</pre>
- *
- * @param out the underlying {@code Appendable} to append escaped output to
- * @return an {@code Appendable} which passes text to {@code out} after escaping it
- * @throws NullPointerException if {@code out} is null.
- */
- @Override public Appendable escape(final Appendable out) {
- checkNotNull(out);
-
- return new Appendable() {
- @Override public Appendable append(CharSequence csq) throws IOException {
- out.append(escape(csq.toString()));
- return this;
- }
-
- @Override public Appendable append(CharSequence csq, int start, int end) throws IOException {
- out.append(escape(csq.subSequence(start, end).toString()));
- return this;
- }
-
- @Override public Appendable append(char c) throws IOException {
- char[] escaped = escape(c);
- if (escaped == null) {
- out.append(c);
- } else {
- for (char e : escaped) {
- out.append(e);
- }
- }
- return this;
- }
- };
- }
-
- /**
- * Returns the escaped form of a given literal string, starting at the given index. This method is
- * called by the {@link #escape(String)} method when it discovers that escaping is required. It is
- * protected to allow subclasses to override the fastpath escaping function to inline their
- * escaping test. See {@link CharEscaperBuilder} for an example usage.
- *
- * @param s the literal string to be escaped
- * @param index the index to start escaping from
- * @return the escaped form of {@code string}
- * @throws NullPointerException if {@code string} is null
- */
- protected String escapeSlow(String s, int index) {
- int slen = s.length();
-
- // Get a destination buffer and setup some loop variables.
- char[] dest = Platform.charBufferFromThreadLocal();
- int destSize = dest.length;
- int destIndex = 0;
- int lastEscape = 0;
-
- // Loop through the rest of the string, replacing when needed into the
- // destination buffer, which gets grown as needed as well.
- for (; index < slen; index++) {
-
- // Get a replacement for the current character.
- char[] r = escape(s.charAt(index));
-
- // If no replacement is needed, just continue.
- if (r == null) continue;
-
- int rlen = r.length;
- int charsSkipped = index - lastEscape;
-
- // This is the size needed to add the replacement, not the full size needed by the string. We
- // only regrow when we absolutely must.
- int sizeNeeded = destIndex + charsSkipped + rlen;
- if (destSize < sizeNeeded) {
- destSize = sizeNeeded + (slen - index) + DEST_PAD;
- dest = growBuffer(dest, destIndex, destSize);
- }
-
- // If we have skipped any characters, we need to copy them now.
- if (charsSkipped > 0) {
- s.getChars(lastEscape, index, dest, destIndex);
- destIndex += charsSkipped;
- }
-
- // Copy the replacement string into the dest buffer as needed.
- if (rlen > 0) {
- System.arraycopy(r, 0, dest, destIndex, rlen);
- destIndex += rlen;
- }
- lastEscape = index + 1;
- }
-
- // Copy leftover characters if there are any.
- int charsLeft = slen - lastEscape;
- if (charsLeft > 0) {
- int sizeNeeded = destIndex + charsLeft;
- if (destSize < sizeNeeded) {
-
- // Regrow and copy, expensive! No padding as this is the final copy.
- dest = growBuffer(dest, destIndex, sizeNeeded);
- }
- s.getChars(lastEscape, slen, dest, destIndex);
- destIndex = sizeNeeded;
- }
- return new String(dest, 0, destIndex);
- }
-
- /**
- * Returns the escaped form of the given character, or {@code null} if this character does not
- * need to be escaped. If an empty array is returned, this effectively strips the input character
- * from the resulting text.
- *
- * <p>If the character does not need to be escaped, this method should return {@code null}, rather
- * than a one-character array containing the character itself. This enables the escaping algorithm
- * to perform more efficiently.
- *
- * <p>An escaper is expected to be able to deal with any {@code char} value, so this method should
- * not throw any exceptions.
- *
- * @param c the character to escape if necessary
- * @return the replacement characters, or {@code null} if no escaping was needed
- */
- protected abstract char[] escape(char c);
-
- /**
- * Helper method to grow the character buffer as needed, this only happens once in a while so it's
- * ok if it's in a method call. If the index passed in is 0 then no copying will be done.
- */
- private static char[] growBuffer(char[] dest, int index, int size) {
- char[] copy = new char[size];
- if (index > 0) {
- System.arraycopy(dest, 0, copy, 0, index);
- }
- return copy;
- }
-
- /**
- * The amount of padding to use when growing the escape buffer.
- */
- private static final int DEST_PAD = 32;
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/CharEscaperBuilder.java b/src/com/android/mail/lib/base/CharEscaperBuilder.java
deleted file mode 100644
index a11eac2..0000000
--- a/src/com/android/mail/lib/base/CharEscaperBuilder.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2006-2007 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.mail.lib.base;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Simple helper class to build a "sparse" array of objects based on the indexes that were added to
- * it. The array will be from 0 to the maximum index given. All non-set indexes will contain null
- * (so it's not really a sparse array, just a pseudo sparse array). The builder can also return a
- * CharEscaper based on the generated array.
- *
- * @author sven@google.com (Sven Mawson)
- */
-public class CharEscaperBuilder {
- /**
- * Simple decorator that turns an array of replacement char[]s into a CharEscaper, this results in
- * a very fast escape method.
- */
- private static class CharArrayDecorator extends CharEscaper {
- private final char[][] replacements;
- private final int replaceLength;
-
- CharArrayDecorator(char[][] replacements) {
- this.replacements = replacements;
- this.replaceLength = replacements.length;
- }
-
- /*
- * Overriding escape method to be slightly faster for this decorator. We test the replacements
- * array directly, saving a method call.
- */
- @Override public String escape(String s) {
- int slen = s.length();
- for (int index = 0; index < slen; index++) {
- char c = s.charAt(index);
- if (c < replacements.length && replacements[c] != null) {
- return escapeSlow(s, index);
- }
- }
- return s;
- }
-
- @Override protected char[] escape(char c) {
- return c < replaceLength ? replacements[c] : null;
- }
- }
-
- // Replacement mappings.
- private final Map<Character, String> map;
-
- // The highest index we've seen so far.
- private int max = -1;
-
- /**
- * Construct a new sparse array builder.
- */
- public CharEscaperBuilder() {
- this.map = new HashMap<Character, String>();
- }
-
- /**
- * Add a new mapping from an index to an object to the escaping.
- */
- public CharEscaperBuilder addEscape(char c, String r) {
- map.put(c, r);
- if (c > max) {
- max = c;
- }
- return this;
- }
-
- /**
- * Add multiple mappings at once for a particular index.
- */
- public CharEscaperBuilder addEscapes(char[] cs, String r) {
- for (char c : cs) {
- addEscape(c, r);
- }
- return this;
- }
-
- /**
- * Convert this builder into an array of char[]s where the maximum index is the value of the
- * highest character that has been seen. The array will be sparse in the sense that any unseen
- * index will default to null.
- *
- * @return a "sparse" array that holds the replacement mappings.
- */
- public char[][] toArray() {
- char[][] result = new char[max + 1][];
- for (Map.Entry<Character, String> entry : map.entrySet()) {
- result[entry.getKey()] = entry.getValue().toCharArray();
- }
- return result;
- }
-
- /**
- * Convert this builder into a char escaper which is just a decorator around the underlying array
- * of replacement char[]s.
- *
- * @return an escaper that escapes based on the underlying array.
- */
- public CharEscaper toEscaper() {
- return new CharArrayDecorator(toArray());
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/CharEscapers.java b/src/com/android/mail/lib/base/CharEscapers.java
deleted file mode 100644
index 7a76ffe..0000000
--- a/src/com/android/mail/lib/base/CharEscapers.java
+++ /dev/null
@@ -1,1102 +0,0 @@
-/*
- * Copyright (C) 2006 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.mail.lib.base;
-
-import static com.android.mail.lib.base.Preconditions.checkNotNull;
-
-import java.io.IOException;
-
-/**
- * Utility functions for dealing with {@code CharEscaper}s, and some commonly
- * used {@code CharEscaper} instances.
- *
- * @author sven@google.com (Sven Mawson)
- * @author laurence@google.com (Laurence Gonsalves)
- */
-public final class CharEscapers {
- private CharEscapers() {}
-
- // TODO(matevossian): To implementors of escapers --
- // For each xxxEscaper method, please add links to external
- // reference pages that we consider authoritative for what
- // that escaper should exactly be doing.
-
- /**
- * Performs no escaping.
- */
- private static final CharEscaper NULL_ESCAPER = new CharEscaper() {
- @Override
- public String escape(String string) {
- checkNotNull(string);
- return string;
- }
-
- @Override
- public Appendable escape(final Appendable out) {
- checkNotNull(out);
-
- // we can't simply return out because the CharEscaper contract says that
- // the returned Appendable will throw a NullPointerException if asked to
- // append null.
- return new Appendable() {
- @Override public Appendable append(CharSequence csq) throws IOException {
- checkNotNull(csq);
- out.append(csq);
- return this;
- }
-
- @Override public Appendable append(CharSequence csq, int start, int end)
- throws IOException {
- checkNotNull(csq);
- out.append(csq, start, end);
- return this;
- }
-
- @Override public Appendable append(char c) throws IOException {
- out.append(c);
- return this;
- }
- };
- }
-
- @Override
- protected char[] escape(char c) {
- return null;
- }
- };
-
- /**
- * Returns a {@link CharEscaper} that does no escaping.
- */
- public static CharEscaper nullEscaper() {
- return NULL_ESCAPER;
- }
-
- /**
- * Returns a {@link CharEscaper} instance that escapes special characters in a
- * string so it can safely be included in an XML document in either element
- * content or attribute values.
- *
- * <p><b>Note</b></p>: silently removes null-characters and control
- * characters, as there is no way to represent them in XML.
- */
- public static CharEscaper xmlEscaper() {
- return XML_ESCAPER;
- }
-
- /**
- * Escapes special characters from a string so it can safely be included in an
- * XML document in either element content or attribute values. Also removes
- * null-characters and control characters, as there is no way to represent
- * them in XML.
- */
- private static final CharEscaper XML_ESCAPER = newBasicXmlEscapeBuilder()
- .addEscape('"', """)
- .addEscape('\'', "'")
- .toEscaper();
-
- /**
- * Returns a {@link CharEscaper} instance that escapes special characters in a
- * string so it can safely be included in an XML document in element content.
- *
- * <p><b>Note</b></p>: double and single quotes are not escaped, so it is not
- * safe to use this escaper to escape attribute values. Use the
- * {@link #xmlEscaper()} escaper to escape attribute values or if you are
- * unsure. Also silently removes non-whitespace control characters, as there
- * is no way to represent them in XML.
- */
- public static CharEscaper xmlContentEscaper() {
- return XML_CONTENT_ESCAPER;
- }
-
- /**
- * Escapes special characters from a string so it can safely be included in an
- * XML document in element content. Note that quotes are <em>not</em>
- * escaped, so <em>this is not safe for use in attribute values</em>. Use
- * {@link #XML_ESCAPER} for attribute values, or if you are unsure. Also
- * removes non-whitespace control characters, as there is no way to represent
- * them in XML.
- */
- private static final CharEscaper XML_CONTENT_ESCAPER =
- newBasicXmlEscapeBuilder().toEscaper();
-
- /**
- * Returns a {@link CharEscaper} instance that escapes special characters in a
- * string so it can safely be included in an HTML document in either element
- * content or attribute values.
- *
- * <p><b>Note</b></p>: alters non-ASCII and control characters.
- *
- * The entity list was taken from:
- * <a href="http://www.w3.org/TR/html4/sgml/entities.html">here</a>
- */
- public static CharEscaper htmlEscaper() {
- return HtmlEscaperHolder.HTML_ESCAPER;
- }
-
- /**
- * A lazy initialization holder for HTML_ESCAPER.
- */
- private static class HtmlEscaperHolder {
- private static final CharEscaper HTML_ESCAPER
- = new HtmlCharEscaper(new CharEscaperBuilder()
- .addEscape('"', """)
- .addEscape('\'', "'")
- .addEscape('&', "&")
- .addEscape('<', "<")
- .addEscape('>', ">")
- .addEscape('\u00A0', " ")
- .addEscape('\u00A1', "¡")
- .addEscape('\u00A2', "¢")
- .addEscape('\u00A3', "£")
- .addEscape('\u00A4', "¤")
- .addEscape('\u00A5', "¥")
- .addEscape('\u00A6', "¦")
- .addEscape('\u00A7', "§")
- .addEscape('\u00A8', "¨")
- .addEscape('\u00A9', "©")
- .addEscape('\u00AA', "ª")
- .addEscape('\u00AB', "«")
- .addEscape('\u00AC', "¬")
- .addEscape('\u00AD', "­")
- .addEscape('\u00AE', "®")
- .addEscape('\u00AF', "¯")
- .addEscape('\u00B0', "°")
- .addEscape('\u00B1', "±")
- .addEscape('\u00B2', "²")
- .addEscape('\u00B3', "³")
- .addEscape('\u00B4', "´")
- .addEscape('\u00B5', "µ")
- .addEscape('\u00B6', "¶")
- .addEscape('\u00B7', "·")
- .addEscape('\u00B8', "¸")
- .addEscape('\u00B9', "¹")
- .addEscape('\u00BA', "º")
- .addEscape('\u00BB', "»")
- .addEscape('\u00BC', "¼")
- .addEscape('\u00BD', "½")
- .addEscape('\u00BE', "¾")
- .addEscape('\u00BF', "¿")
- .addEscape('\u00C0', "À")
- .addEscape('\u00C1', "Á")
- .addEscape('\u00C2', "Â")
- .addEscape('\u00C3', "Ã")
- .addEscape('\u00C4', "Ä")
- .addEscape('\u00C5', "Å")
- .addEscape('\u00C6', "Æ")
- .addEscape('\u00C7', "Ç")
- .addEscape('\u00C8', "È")
- .addEscape('\u00C9', "É")
- .addEscape('\u00CA', "Ê")
- .addEscape('\u00CB', "Ë")
- .addEscape('\u00CC', "Ì")
- .addEscape('\u00CD', "Í")
- .addEscape('\u00CE', "Î")
- .addEscape('\u00CF', "Ï")
- .addEscape('\u00D0', "Ð")
- .addEscape('\u00D1', "Ñ")
- .addEscape('\u00D2', "Ò")
- .addEscape('\u00D3', "Ó")
- .addEscape('\u00D4', "Ô")
- .addEscape('\u00D5', "Õ")
- .addEscape('\u00D6', "Ö")
- .addEscape('\u00D7', "×")
- .addEscape('\u00D8', "Ø")
- .addEscape('\u00D9', "Ù")
- .addEscape('\u00DA', "Ú")
- .addEscape('\u00DB', "Û")
- .addEscape('\u00DC', "Ü")
- .addEscape('\u00DD', "Ý")
- .addEscape('\u00DE', "Þ")
- .addEscape('\u00DF', "ß")
- .addEscape('\u00E0', "à")
- .addEscape('\u00E1', "á")
- .addEscape('\u00E2', "â")
- .addEscape('\u00E3', "ã")
- .addEscape('\u00E4', "ä")
- .addEscape('\u00E5', "å")
- .addEscape('\u00E6', "æ")
- .addEscape('\u00E7', "ç")
- .addEscape('\u00E8', "è")
- .addEscape('\u00E9', "é")
- .addEscape('\u00EA', "ê")
- .addEscape('\u00EB', "ë")
- .addEscape('\u00EC', "ì")
- .addEscape('\u00ED', "í")
- .addEscape('\u00EE', "î")
- .addEscape('\u00EF', "ï")
- .addEscape('\u00F0', "ð")
- .addEscape('\u00F1', "ñ")
- .addEscape('\u00F2', "ò")
- .addEscape('\u00F3', "ó")
- .addEscape('\u00F4', "ô")
- .addEscape('\u00F5', "õ")
- .addEscape('\u00F6', "ö")
- .addEscape('\u00F7', "÷")
- .addEscape('\u00F8', "ø")
- .addEscape('\u00F9', "ù")
- .addEscape('\u00FA', "ú")
- .addEscape('\u00FB', "û")
- .addEscape('\u00FC', "ü")
- .addEscape('\u00FD', "ý")
- .addEscape('\u00FE', "þ")
- .addEscape('\u00FF', "ÿ")
- .addEscape('\u0152', "Œ")
- .addEscape('\u0153', "œ")
- .addEscape('\u0160', "Š")
- .addEscape('\u0161', "š")
- .addEscape('\u0178', "Ÿ")
- .addEscape('\u0192', "ƒ")
- .addEscape('\u02C6', "ˆ")
- .addEscape('\u02DC', "˜")
- .addEscape('\u0391', "Α")
- .addEscape('\u0392', "Β")
- .addEscape('\u0393', "Γ")
- .addEscape('\u0394', "Δ")
- .addEscape('\u0395', "Ε")
- .addEscape('\u0396', "Ζ")
- .addEscape('\u0397', "Η")
- .addEscape('\u0398', "Θ")
- .addEscape('\u0399', "Ι")
- .addEscape('\u039A', "Κ")
- .addEscape('\u039B', "Λ")
- .addEscape('\u039C', "Μ")
- .addEscape('\u039D', "Ν")
- .addEscape('\u039E', "Ξ")
- .addEscape('\u039F', "Ο")
- .addEscape('\u03A0', "Π")
- .addEscape('\u03A1', "Ρ")
- .addEscape('\u03A3', "Σ")
- .addEscape('\u03A4', "Τ")
- .addEscape('\u03A5', "Υ")
- .addEscape('\u03A6', "Φ")
- .addEscape('\u03A7', "Χ")
- .addEscape('\u03A8', "Ψ")
- .addEscape('\u03A9', "Ω")
- .addEscape('\u03B1', "α")
- .addEscape('\u03B2', "β")
- .addEscape('\u03B3', "γ")
- .addEscape('\u03B4', "δ")
- .addEscape('\u03B5', "ε")
- .addEscape('\u03B6', "ζ")
- .addEscape('\u03B7', "η")
- .addEscape('\u03B8', "θ")
- .addEscape('\u03B9', "ι")
- .addEscape('\u03BA', "κ")
- .addEscape('\u03BB', "λ")
- .addEscape('\u03BC', "μ")
- .addEscape('\u03BD', "ν")
- .addEscape('\u03BE', "ξ")
- .addEscape('\u03BF', "ο")
- .addEscape('\u03C0', "π")
- .addEscape('\u03C1', "ρ")
- .addEscape('\u03C2', "ς")
- .addEscape('\u03C3', "σ")
- .addEscape('\u03C4', "τ")
- .addEscape('\u03C5', "υ")
- .addEscape('\u03C6', "φ")
- .addEscape('\u03C7', "χ")
- .addEscape('\u03C8', "ψ")
- .addEscape('\u03C9', "ω")
- .addEscape('\u03D1', "ϑ")
- .addEscape('\u03D2', "ϒ")
- .addEscape('\u03D6', "ϖ")
- .addEscape('\u2002', " ")
- .addEscape('\u2003', " ")
- .addEscape('\u2009', " ")
- .addEscape('\u200C', "‌")
- .addEscape('\u200D', "‍")
- .addEscape('\u200E', "‎")
- .addEscape('\u200F', "‏")
- .addEscape('\u2013', "–")
- .addEscape('\u2014', "—")
- .addEscape('\u2018', "‘")
- .addEscape('\u2019', "’")
- .addEscape('\u201A', "‚")
- .addEscape('\u201C', "“")
- .addEscape('\u201D', "”")
- .addEscape('\u201E', "„")
- .addEscape('\u2020', "†")
- .addEscape('\u2021', "‡")
- .addEscape('\u2022', "•")
- .addEscape('\u2026', "…")
- .addEscape('\u2030', "‰")
- .addEscape('\u2032', "′")
- .addEscape('\u2033', "″")
- .addEscape('\u2039', "‹")
- .addEscape('\u203A', "›")
- .addEscape('\u203E', "‾")
- .addEscape('\u2044', "⁄")
- .addEscape('\u20AC', "€")
- .addEscape('\u2111', "ℑ")
- .addEscape('\u2118', "℘")
- .addEscape('\u211C', "ℜ")
- .addEscape('\u2122', "™")
- .addEscape('\u2135', "ℵ")
- .addEscape('\u2190', "←")
- .addEscape('\u2191', "↑")
- .addEscape('\u2192', "→")
- .addEscape('\u2193', "↓")
- .addEscape('\u2194', "↔")
- .addEscape('\u21B5', "↵")
- .addEscape('\u21D0', "⇐")
- .addEscape('\u21D1', "⇑")
- .addEscape('\u21D2', "⇒")
- .addEscape('\u21D3', "⇓")
- .addEscape('\u21D4', "⇔")
- .addEscape('\u2200', "∀")
- .addEscape('\u2202', "∂")
- .addEscape('\u2203', "∃")
- .addEscape('\u2205', "∅")
- .addEscape('\u2207', "∇")
- .addEscape('\u2208', "∈")
- .addEscape('\u2209', "∉")
- .addEscape('\u220B', "∋")
- .addEscape('\u220F', "∏")
- .addEscape('\u2211', "∑")
- .addEscape('\u2212', "−")
- .addEscape('\u2217', "∗")
- .addEscape('\u221A', "√")
- .addEscape('\u221D', "∝")
- .addEscape('\u221E', "∞")
- .addEscape('\u2220', "∠")
- .addEscape('\u2227', "∧")
- .addEscape('\u2228', "∨")
- .addEscape('\u2229', "∩")
- .addEscape('\u222A', "∪")
- .addEscape('\u222B', "∫")
- .addEscape('\u2234', "∴")
- .addEscape('\u223C', "∼")
- .addEscape('\u2245', "≅")
- .addEscape('\u2248', "≈")
- .addEscape('\u2260', "≠")
- .addEscape('\u2261', "≡")
- .addEscape('\u2264', "≤")
- .addEscape('\u2265', "≥")
- .addEscape('\u2282', "⊂")
- .addEscape('\u2283', "⊃")
- .addEscape('\u2284', "⊄")
- .addEscape('\u2286', "⊆")
- .addEscape('\u2287', "⊇")
- .addEscape('\u2295', "⊕")
- .addEscape('\u2297', "⊗")
- .addEscape('\u22A5', "⊥")
- .addEscape('\u22C5', "⋅")
- .addEscape('\u2308', "⌈")
- .addEscape('\u2309', "⌉")
- .addEscape('\u230A', "⌊")
- .addEscape('\u230B', "⌋")
- .addEscape('\u2329', "⟨")
- .addEscape('\u232A', "⟩")
- .addEscape('\u25CA', "◊")
- .addEscape('\u2660', "♠")
- .addEscape('\u2663', "♣")
- .addEscape('\u2665', "♥")
- .addEscape('\u2666', "♦")
- .toArray());
- }
-
- /**
- * Returns a {@link CharEscaper} instance that escapes special characters in a
- * string so it can safely be included in an HTML document in either element
- * content or attribute values.
- *
- * <p><b>Note</b></p>: does not alter non-ASCII and control characters.
- */
- public static CharEscaper asciiHtmlEscaper() {
- return ASCII_HTML_ESCAPER;
- }
-
- /**
- * Escapes special characters from a string so it can safely be included in an
- * HTML document in either element content or attribute values. Does
- * <em>not</em> alter non-ASCII characters or control characters.
- */
- private static final CharEscaper ASCII_HTML_ESCAPER = new CharEscaperBuilder()
- .addEscape('"', """)
- .addEscape('\'', "'")
- .addEscape('&', "&")
- .addEscape('<', "<")
- .addEscape('>', ">")
- .toEscaper();
-
- /**
- * Returns an {@link Escaper} instance that escapes Java chars so they can be
- * safely included in URIs. For details on escaping URIs, see section 2.4 of
- * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>.
- *
- * <p>When encoding a String, the following rules apply:
- * <ul>
- * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0"
- * through "9" remain the same.
- * <li>The special characters ".", "-", "*", and "_" remain the same.
- * <li>The space character " " is converted into a plus sign "+".
- * <li>All other characters are converted into one or more bytes using UTF-8
- * encoding and each byte is then represented by the 3-character string
- * "%XY", where "XY" is the two-digit, uppercase, hexadecimal
- * representation of the byte value.
- * <ul>
- *
- * <p><b>Note</b>: Unlike other escapers, URI escapers produce uppercase
- * hexadecimal sequences. From <a href="http://www.ietf.org/rfc/rfc3986.txt">
- * RFC 3986</a>:<br>
- * <i>"URI producers and normalizers should use uppercase hexadecimal digits
- * for all percent-encodings."</i>
- *
- * <p>This escaper has identical behavior to (but is potentially much faster
- * than):
- * <ul>
- * <li>{@link com.google.httputil.FastURLEncoder#encode(String)}
- * <li>{@link com.google.httputil.FastURLEncoder#encode(String,String)}
- * with the encoding name "UTF-8"
- * <li>{@link java.net.URLEncoder#encode(String, String)}
- * with the encoding name "UTF-8"
- * </ul>
- *
- * <p>This method is equivalent to {@code uriEscaper(true)}.
- */
- public static Escaper uriEscaper() {
- return uriEscaper(true);
- }
-
- /**
- * Returns an {@link Escaper} instance that escapes Java chars so they can be
- * safely included in URI path segments. For details on escaping URIs, see
- * section 2.4 of <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
- *
- * <p>When encoding a String, the following rules apply:
- * <ul>
- * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0"
- * through "9" remain the same.
- * <li>The unreserved characters ".", "-", "~", and "_" remain the same.
- * <li>The general delimiters "@" and ":" remain the same.
- * <li>The subdelimiters "!", "$", "&", "'", "(", ")", "*", ",", ";",
- * and "=" remain the same.
- * <li>The space character " " is converted into %20.
- * <li>All other characters are converted into one or more bytes using UTF-8
- * encoding and each byte is then represented by the 3-character string
- * "%XY", where "XY" is the two-digit, uppercase, hexadecimal
- * representation of the byte value.
- * </ul>
- *
- * <p><b>Note</b>: Unlike other escapers, URI escapers produce uppercase
- * hexadecimal sequences. From <a href="http://www.ietf.org/rfc/rfc3986.txt">
- * RFC 3986</a>:<br>
- * <i>"URI producers and normalizers should use uppercase hexadecimal digits
- * for all percent-encodings."</i>
- */
- public static Escaper uriPathEscaper() {
- return URI_PATH_ESCAPER;
- }
-
- /**
- * Returns an {@link Escaper} instance that escapes Java chars so they can be
- * safely included in URI query string segments. When the query string
- * consists of a sequence of name=value pairs separated by &, the names
- * and values should be individually encoded. If you escape an entire query
- * string in one pass with this escaper, then the "=" and "&" characters
- * used as separators will also be escaped.
- *
- * <p>This escaper is also suitable for escaping fragment identifiers.
- *
- * <p>For details on escaping URIs, see
- * section 2.4 of <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
- *
- * <p>When encoding a String, the following rules apply:
- * <ul>
- * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0"
- * through "9" remain the same.
- * <li>The unreserved characters ".", "-", "~", and "_" remain the same.
- * <li>The general delimiters "@" and ":" remain the same.
- * <li>The path delimiters "/" and "?" remain the same.
- * <li>The subdelimiters "!", "$", "'", "(", ")", "*", ",", and ";",
- * remain the same.
- * <li>The space character " " is converted into %20.
- * <li>The equals sign "=" is converted into %3D.
- * <li>The ampersand "&" is converted into %26.
- * <li>All other characters are converted into one or more bytes using UTF-8
- * encoding and each byte is then represented by the 3-character string
- * "%XY", where "XY" is the two-digit, uppercase, hexadecimal
- * representation of the byte value.
- * </ul>
- *
- * <p><b>Note</b>: Unlike other escapers, URI escapers produce uppercase
- * hexadecimal sequences. From <a href="http://www.ietf.org/rfc/rfc3986.txt">
- * RFC 3986</a>:<br>
- * <i>"URI producers and normalizers should use uppercase hexadecimal digits
- * for all percent-encodings."</i>
- *
- * <p>This method is equivalent to {@code uriQueryStringEscaper(false)}.
- */
- public static Escaper uriQueryStringEscaper() {
- return uriQueryStringEscaper(false);
- }
-
- /**
- * Returns a {@link Escaper} instance that escapes Java characters so they can
- * be safely included in URIs. For details on escaping URIs, see section 2.4
- * of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>.
- *
- * <p>When encoding a String, the following rules apply:
- * <ul>
- * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0"
- * through "9" remain the same.
- * <li>The special characters ".", "-", "*", and "_" remain the same.
- * <li>If {@code plusForSpace} was specified, the space character " " is
- * converted into a plus sign "+". Otherwise it is converted into "%20".
- * <li>All other characters are converted into one or more bytes using UTF-8
- * encoding and each byte is then represented by the 3-character string
- * "%XY", where "XY" is the two-digit, uppercase, hexadecimal
- * representation of the byte value.
- * </ul>
- *
- * <p><b>Note</b>: Unlike other escapers, URI escapers produce uppercase
- * hexadecimal sequences. From <a href="http://www.ietf.org/rfc/rfc3986.txt">
- * RFC 3986</a>:<br>
- * <i>"URI producers and normalizers should use uppercase hexadecimal digits
- * for all percent-encodings."</i>
- *
- * @param plusForSpace if {@code true} space is escaped to {@code +} otherwise
- * it is escaped to {@code %20}. Although common, the escaping of
- * spaces as plus signs has a very ambiguous status in the relevant
- * specifications. You should prefer {@code %20} unless you are doing
- * exact character-by-character comparisons of URLs and backwards
- * compatibility requires you to use plus signs.
- *
- * @see #uriEscaper()
- */
- public static Escaper uriEscaper(boolean plusForSpace) {
- return plusForSpace ? URI_ESCAPER : URI_ESCAPER_NO_PLUS;
- }
-
- /**
- * Returns an {@link Escaper} instance that escapes Java chars so they can be
- * safely included in URI query string segments. When the query string
- * consists of a sequence of name=value pairs separated by &, the names
- * and values should be individually encoded. If you escape an entire query
- * string in one pass with this escaper, then the "=" and "&" characters
- * used as separators will also be escaped.
- *
- * <p>This escaper is also suitable for escaping fragment identifiers.
- *
- * <p>For details on escaping URIs, see
- * section 2.4 of <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
- *
- * <p>When encoding a String, the following rules apply:
- * <ul>
- * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0"
- * through "9" remain the same.
- * <li>The unreserved characters ".", "-", "~", and "_" remain the same.
- * <li>The general delimiters "@" and ":" remain the same.
- * <li>The path delimiters "/" and "?" remain the same.
- * <li>The subdelimiters "!", "$", "'", "(", ")", "*", ",", and ";",
- * remain the same.
- * <li>If {@code plusForSpace} was specified, the space character " " is
- * converted into a plus sign "+". Otherwise it is converted into "%20".
- * <li>The equals sign "=" is converted into %3D.
- * <li>The ampersand "&" is converted into %26.
- * <li>All other characters are converted into one or more bytes using UTF-8
- * encoding and each byte is then represented by the 3-character string
- * "%XY", where "XY" is the two-digit, uppercase, hexadecimal
- * representation of the byte value.
- * </ul>
- *
- * <p><b>Note</b>: Unlike other escapers, URI escapers produce uppercase
- * hexadecimal sequences. From <a href="http://www.ietf.org/rfc/rfc3986.txt">
- * RFC 3986</a>:<br>
- * <i>"URI producers and normalizers should use uppercase hexadecimal digits
- * for all percent-encodings."</i>
- *
- * @param plusForSpace if {@code true} space is escaped to {@code +} otherwise
- * it is escaped to {@code %20}. Although common, the escaping of
- * spaces as plus signs has a very ambiguous status in the relevant
- * specifications. You should prefer {@code %20} unless you are doing
- * exact character-by-character comparisons of URLs and backwards
- * compatibility requires you to use plus signs.
- *
- * @see #uriQueryStringEscaper()
- */
- public static Escaper uriQueryStringEscaper(boolean plusForSpace) {
- return plusForSpace ?
- URI_QUERY_STRING_ESCAPER_WITH_PLUS : URI_QUERY_STRING_ESCAPER;
- }
-
- private static final Escaper URI_ESCAPER =
- new PercentEscaper(PercentEscaper.SAFECHARS_URLENCODER, true);
-
- private static final Escaper URI_ESCAPER_NO_PLUS =
- new PercentEscaper(PercentEscaper.SAFECHARS_URLENCODER, false);
-
- private static final Escaper URI_PATH_ESCAPER =
- new PercentEscaper(PercentEscaper.SAFEPATHCHARS_URLENCODER, false);
-
- private static final Escaper URI_QUERY_STRING_ESCAPER =
- new PercentEscaper(PercentEscaper.SAFEQUERYSTRINGCHARS_URLENCODER, false);
-
- private static final Escaper URI_QUERY_STRING_ESCAPER_WITH_PLUS =
- new PercentEscaper(PercentEscaper.SAFEQUERYSTRINGCHARS_URLENCODER, true);
-
- /**
- * Returns a {@link Escaper} instance that escapes Java characters in a manner
- * compatible with the C++ webutil/url URL class (the {@code kGoogle1Escape}
- * set).
- *
- * <p>When encoding a String, the following rules apply:
- * <ul>
- * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0"
- * through "9" remain the same.
- * <li>The special characters "!", "(", ")", "*", "-", ".", "_", "~", ",", "/"
- * and ":" remain the same.
- * <li>The space character " " is converted into a plus sign "+".
- * <li>All other characters are converted into one or more bytes using UTF-8
- * encoding and each byte is then represented by the 3-character string
- * "%XY", where "XY" is the two-digit, uppercase, hexadecimal
- * representation of the byte value.
- * </ul>
- *
- * <p><b>Note</b>: Unlike other escapers, URI escapers produce uppercase
- * hexadecimal sequences. From <a href="http://www.ietf.org/rfc/rfc3986.txt">
- * RFC 3986</a>:<br>
- * <i>"URI producers and normalizers should use uppercase hexadecimal digits
- * for all percent-encodings."</i>
- *
- * <p><b>Note</b>: This escaper is a special case and is <em>not
- * compliant</em> with <a href="http://www.ietf.org/rfc/rfc2396.txt">
- * RFC 2396</a>. Specifically it will not escape "/", ":" and ",". This is
- * only provided for certain limited use cases and you should favor using
- * {@link #uriEscaper()} whenever possible.
- */
- public static Escaper cppUriEscaper() {
- return CPP_URI_ESCAPER;
- }
-
- // Based on comments from FastURLEncoder:
- // These octets mimic the ones escaped by the C++ webutil/url URL class --
- // the kGoogle1Escape set.
- // To produce the same escaping as C++, use this set with the plusForSpace
- // option.
- // WARNING: Contrary to RFC 2396 ",", "/" and ":" are listed as safe here.
- private static final Escaper CPP_URI_ESCAPER =
- new PercentEscaper("!()*-._~,/:", true);
-
- /**
- * Returns a {@link CharEscaper} instance that escapes special characters in a
- * string so it can safely be included in a Java string literal.
- *
- * <p><b>Note</b></p>: does not escape single quotes, so use the escaper
- * returned by {@link #javaCharEscaper()} if you are generating char
- * literals or if you are unsure.
- */
- public static CharEscaper javaStringEscaper() {
- return JAVA_STRING_ESCAPER;
- }
-
- /**
- * Escapes special characters from a string so it can safely be included in a
- * Java string literal. Does <em>not</em> escape single-quotes, so use
- * JAVA_CHAR_ESCAPE if you are generating char literals, or if you are unsure.
- *
- * <p>Note that non-ASCII characters will be octal or Unicode escaped.
- */
- private static final CharEscaper JAVA_STRING_ESCAPER
- = new JavaCharEscaper(new CharEscaperBuilder()
- .addEscape('\b', "\\b")
- .addEscape('\f', "\\f")
- .addEscape('\n', "\\n")
- .addEscape('\r', "\\r")
- .addEscape('\t', "\\t")
- .addEscape('\"', "\\\"")
- .addEscape('\\', "\\\\")
- .toArray());
-
- /**
- * Returns a {@link CharEscaper} instance that escapes special characters in a
- * string so it can safely be included in a Java char or string literal. The
- * behavior of this escaper is the same as that of the
- * {@link #javaStringEscaper()}, except it also escapes single quotes.
- */
- public static CharEscaper javaCharEscaper() {
- return JAVA_CHAR_ESCAPER;
- }
-
- /**
- * Escapes special characters from a string so it can safely be included in a
- * Java char literal or string literal.
- *
- * <p>Note that non-ASCII characters will be octal or Unicode escaped.
- *
- * <p>This is the same as {@link #JAVA_STRING_ESCAPER}, except that it escapes
- * single quotes.
- */
- private static final CharEscaper JAVA_CHAR_ESCAPER
- = new JavaCharEscaper(new CharEscaperBuilder()
- .addEscape('\b', "\\b")
- .addEscape('\f', "\\f")
- .addEscape('\n', "\\n")
- .addEscape('\r', "\\r")
- .addEscape('\t', "\\t")
- .addEscape('\'', "\\'")
- .addEscape('\"', "\\\"")
- .addEscape('\\', "\\\\")
- .toArray());
-
- /**
- * Returns a {@link CharEscaper} instance that replaces non-ASCII characters
- * in a string with their Unicode escape sequences ({@code \\uxxxx} where
- * {@code xxxx} is a hex number). Existing escape sequences won't be affected.
- */
- public static CharEscaper javaStringUnicodeEscaper() {
- return JAVA_STRING_UNICODE_ESCAPER;
- }
-
- /**
- * Escapes each non-ASCII character in with its Unicode escape sequence
- * {@code \\uxxxx} where {@code xxxx} is a hex number. Existing escape
- * sequences won't be affected.
- */
- private static final CharEscaper JAVA_STRING_UNICODE_ESCAPER
- = new CharEscaper() {
- @Override protected char[] escape(char c) {
- if (c <= 127) {
- return null;
- }
-
- char[] r = new char[6];
- r[5] = HEX_DIGITS[c & 15];
- c >>>= 4;
- r[4] = HEX_DIGITS[c & 15];
- c >>>= 4;
- r[3] = HEX_DIGITS[c & 15];
- c >>>= 4;
- r[2] = HEX_DIGITS[c & 15];
- r[1] = 'u';
- r[0] = '\\';
- return r;
- }
- };
-
- /**
- * Returns a {@link CharEscaper} instance that escapes special characters from
- * a string so it can safely be included in a Python string literal. Does not
- * have any special handling for non-ASCII characters.
- */
- public static CharEscaper pythonEscaper() {
- return PYTHON_ESCAPER;
- }
-
- /**
- * Escapes special characters in a string so it can safely be included in a
- * Python string literal. Does not have any special handling for non-ASCII
- * characters.
- */
- private static final CharEscaper PYTHON_ESCAPER = new CharEscaperBuilder()
- // TODO(laurence): perhaps this should escape non-ASCII characters?
- .addEscape('\n', "\\n")
- .addEscape('\r', "\\r")
- .addEscape('\t', "\\t")
- .addEscape('\\', "\\\\")
- .addEscape('\"', "\\\"")
- .addEscape('\'', "\\\'")
- .toEscaper();
-
- /**
- * Returns a {@link CharEscaper} instance that escapes non-ASCII characters in
- * a string so it can safely be included in a Javascript string literal.
- * Non-ASCII characters are replaced with their ASCII javascript escape
- * sequences (e.g., \\uhhhh or \xhh).
- */
- public static CharEscaper javascriptEscaper() {
- return JAVASCRIPT_ESCAPER;
- }
-
- /**
- * {@code CharEscaper} to escape javascript strings. Turns all non-ASCII
- * characters into ASCII javascript escape sequences (e.g., \\uhhhh or \xhh).
- */
- private static final CharEscaper JAVASCRIPT_ESCAPER
- = new JavascriptCharEscaper(new CharEscaperBuilder()
- .addEscape('\'', "\\x27")
- .addEscape('"', "\\x22")
- .addEscape('<', "\\x3c")
- .addEscape('=', "\\x3d")
- .addEscape('>', "\\x3e")
- .addEscape('&', "\\x26")
- .addEscape('\b', "\\b")
- .addEscape('\t', "\\t")
- .addEscape('\n', "\\n")
- .addEscape('\f', "\\f")
- .addEscape('\r', "\\r")
- .addEscape('\\', "\\\\")
- .toArray());
-
- private static CharEscaperBuilder newBasicXmlEscapeBuilder() {
- return new CharEscaperBuilder()
- .addEscape('&', "&")
- .addEscape('<', "<")
- .addEscape('>', ">")
- .addEscapes(new char[] {
- '\000', '\001', '\002', '\003', '\004',
- '\005', '\006', '\007', '\010', '\013',
- '\014', '\016', '\017', '\020', '\021',
- '\022', '\023', '\024', '\025', '\026',
- '\027', '\030', '\031', '\032', '\033',
- '\034', '\035', '\036', '\037'}, "");
- }
-
- /**
- * Returns a composite {@link CharEscaper} instance that tries to escape
- * characters using a primary {@code CharEscaper} first and falls back to a
- * secondary one if there is no escaping.
- *
- * <p>The returned escaper will attempt to escape each character using the
- * primary escaper, and if the primary escaper has no escaping for that
- * character, it will use the secondary escaper. If the secondary escaper has
- * no escaping for a character either, the original character will be used.
- * If the primary escaper has an escape for a character, the secondary escaper
- * will not be used at all for that character; the escaped output of the
- * primary is not run through the secondary. For a case where you would like
- * to first escape with one escaper, and then with another, it is recommended
- * that you call each escaper in order.
- *
- * @param primary The primary {@code CharEscaper} to use
- * @param secondary The secondary {@code CharEscaper} to use if the first one
- * has no escaping rule for a character
- * @throws NullPointerException if any of the arguments is null
- */
- public static CharEscaper fallThrough(CharEscaper primary,
- CharEscaper secondary) {
- checkNotNull(primary);
- checkNotNull(secondary);
- return new FallThroughCharEscaper(primary, secondary);
- }
-
- /**
- * A fast {@link CharEscaper} that uses an array of replacement characters and
- * a range of safe characters. It overrides {@link #escape(String)} to improve
- * performance. Rough benchmarking shows that this almost doubles the speed
- * when processing strings that do not require escaping (providing the escape
- * test itself is efficient).
- */
- private static abstract class FastCharEscaper extends CharEscaper {
-
- protected final char[][] replacements;
- protected final int replacementLength;
- protected final char safeMin;
- protected final char safeMax;
-
- public FastCharEscaper(char[][] replacements, char safeMin, char safeMax) {
- this.replacements = replacements;
- this.replacementLength = replacements.length;
- this.safeMin = safeMin;
- this.safeMax = safeMax;
- }
-
- /** Overridden for performance (see {@link FastCharEscaper}). */
- @Override public String escape(String s) {
- int slen = s.length();
- for (int index = 0; index < slen; index++) {
- char c = s.charAt(index);
- if ((c < replacementLength && replacements[c] != null)
- || c < safeMin || c > safeMax) {
- return escapeSlow(s, index);
- }
- }
- return s;
- }
- }
-
- /**
- * Escaper for Java character escaping, contains both an array and a
- * backup function. We're not overriding the array decorator because we
- * want to keep this as fast as possible, so no calls to super.escape first.
- */
- private static class JavaCharEscaper extends FastCharEscaper {
-
- public JavaCharEscaper(char[][] replacements) {
- super(replacements, ' ', '~');
- }
-
- @Override protected char[] escape(char c) {
- // First check if our array has a valid escaping.
- if (c < replacementLength) {
- char[] r = replacements[c];
- if (r != null) {
- return r;
- }
- }
-
- // This range is un-escaped.
- if (safeMin <= c && c <= safeMax) {
- return null;
- }
-
- if (c <= 0xFF) {
- // Convert c to an octal-escaped string.
- // Equivalent to String.format("\\%03o", (int)c);
- char[] r = new char[4];
- r[0] = '\\';
- r[3] = HEX_DIGITS[c & 7];
- c >>>= 3;
- r[2] = HEX_DIGITS[c & 7];
- c >>>= 3;
- r[1] = HEX_DIGITS[c & 7];
- return r;
- }
-
- // Convert c to a hex-escaped string.
- // Equivalent to String.format("\\u%04x", (int)c);
- char[] r = new char[6];
- r[0] = '\\';
- r[1] = 'u';
- r[5] = HEX_DIGITS[c & 15];
- c >>>= 4;
- r[4] = HEX_DIGITS[c & 15];
- c >>>= 4;
- r[3] = HEX_DIGITS[c & 15];
- c >>>= 4;
- r[2] = HEX_DIGITS[c & 15];
- return r;
- }
- }
-
- /**
- * Escaper for javascript character escaping, contains both an array and a
- * backup function. We're not overriding the array decorator because we
- * want to keep this as fast as possible, so no calls to super.escape first.
- */
- private static class JavascriptCharEscaper extends FastCharEscaper {
-
- public JavascriptCharEscaper(char[][] replacements) {
- super(replacements, ' ', '~');
- }
-
- @Override protected char[] escape(char c) {
- // First check if our array has a valid escaping.
- if (c < replacementLength) {
- char[] r = replacements[c];
- if (r != null) {
- return r;
- }
- }
-
- // This range is unescaped.
- if (safeMin <= c && c <= safeMax) {
- return null;
- }
-
- // we can do a 2 digit hex escape for chars less that 0x100
- if (c < 0x100) {
- char[] r = new char[4];
- r[3] = HEX_DIGITS[c & 0xf];
- c >>>= 4;
- r[2] = HEX_DIGITS[c & 0xf];
- r[1] = 'x';
- r[0] = '\\';
- return r;
- }
-
- // 4 digit hex escape everything else
- char[] r = new char[6];
- r[5] = HEX_DIGITS[c & 0xf];
- c >>>= 4;
- r[4] = HEX_DIGITS[c & 0xf];
- c >>>= 4;
- r[3] = HEX_DIGITS[c & 0xf];
- c >>>= 4;
- r[2] = HEX_DIGITS[c & 0xf];
- r[1] = 'u';
- r[0] = '\\';
- return r;
- }
- }
-
- /**
- * Escaper for HTML character escaping, contains both an array and a
- * backup function. We're not overriding the array decorator because we
- * want to keep this as fast as possible, so no calls to super.escape first.
- */
- private static class HtmlCharEscaper extends FastCharEscaper {
-
- public HtmlCharEscaper(char[][] replacements) {
- super(replacements, Character.MIN_VALUE, '~');
- }
-
- @Override protected char[] escape(char c) {
- // First check if our array has a valid escaping.
- if (c < replacementLength) {
- char[] r = replacements[c];
- if (r != null) {
- return r;
- }
- }
-
- // ~ is ASCII 126, the highest value char that does not need
- // to be escaped
- if (c <= safeMax) {
- return null;
- }
-
- int index;
- if (c < 1000) {
- index = 4;
- } else if (c < 10000) {
- index = 5;
- } else {
- index = 6;
- }
- char[] result = new char[index + 2];
- result[0] = '&';
- result[1] = '#';
- result[index + 1] = ';';
-
- // TODO(sven): Convert this to a sequence of shifts/additions
- // to avoid the division and modulo operators.
- int intValue = c;
- for (; index > 1; index--) {
- result[index] = HEX_DIGITS[intValue % 10];
- intValue /= 10;
- }
- return result;
- }
- }
-
- /**
- * A composite {@code CharEscaper} object that tries to escape characters
- * using a primary {@code CharEscaper} first and falls back to a secondary
- * one if there is no escaping.
- */
- private static class FallThroughCharEscaper extends CharEscaper {
-
- private final CharEscaper primary;
- private final CharEscaper secondary;
-
- public FallThroughCharEscaper(CharEscaper primary, CharEscaper secondary) {
- this.primary = primary;
- this.secondary = secondary;
- }
-
- @Override
- protected char[] escape(char c) {
- char result[] = primary.escape(c);
- if (result == null) {
- result = secondary.escape(c);
- }
- return result;
- }
- }
-
- private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/CharMatcher.java b/src/com/android/mail/lib/base/CharMatcher.java
deleted file mode 100644
index 9ff3161..0000000
--- a/src/com/android/mail/lib/base/CharMatcher.java
+++ /dev/null
@@ -1,1124 +0,0 @@
-/*
- * Copyright (C) 2008 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.mail.lib.base;
-
-import static com.android.mail.lib.base.Preconditions.checkArgument;
-import static com.android.mail.lib.base.Preconditions.checkNotNull;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Determines a true or false value for any Java {@code char} value, just as
- * {@link Predicate} does for any {@link Object}. Also offers basic text
- * processing methods based on this function. Implementations are strongly
- * encouraged to be side-effect-free and immutable.
- *
- * <p>Throughout the documentation of this class, the phrase "matching
- * character" is used to mean "any character {@code c} for which {@code
- * this.matches(c)} returns {@code true}".
- *
- * <p><b>Note:</b> This class deals only with {@code char} values; it does not
- * understand supplementary Unicode code points in the range {@code 0x10000} to
- * {@code 0x10FFFF}. Such logical characters are encoded into a {@code String}
- * using surrogate pairs, and a {@code CharMatcher} treats these just as two
- * separate characters.
- *
- * @author Kevin Bourrillion
- * @since 2009.09.15 <b>tentative</b>
- */
-public abstract class CharMatcher implements Predicate<Character> {
-
- // Constants
-
- // Excludes 2000-2000a, which is handled as a range
- private static final String BREAKING_WHITESPACE_CHARS =
- "\t\n\013\f\r \u0085\u1680\u2028\u2029\u205f\u3000";
-
- // Excludes 2007, which is handled as a gap in a pair of ranges
- private static final String NON_BREAKING_WHITESPACE_CHARS =
- "\u00a0\u180e\u202f";
-
- /**
- * Determines whether a character is whitespace according to the latest
- * Unicode standard, as illustrated
- * <a href="http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5Cp%7Bwhitespace%7D">here</a>.
- * This is not the same definition used by other Java APIs. See a comparison
- * of several definitions of "whitespace" at
- * <a href="TODO">(TODO)</a>.
- *
- * <p><b>Note:</b> as the Unicode definition evolves, we will modify this
- * constant to keep it up to date.
- */
- public static final CharMatcher WHITESPACE =
- anyOf(BREAKING_WHITESPACE_CHARS + NON_BREAKING_WHITESPACE_CHARS)
- .or(inRange('\u2000', '\u200a'));
-
- /**
- * Determines whether a character is a breaking whitespace (that is,
- * a whitespace which can be interpreted as a break between words
- * for formatting purposes). See {@link #WHITESPACE} for a discussion
- * of that term.
- *
- * @since 2010.01.04 <b>tentative</b>
- */
- public static final CharMatcher BREAKING_WHITESPACE =
- anyOf(BREAKING_WHITESPACE_CHARS)
- .or(inRange('\u2000', '\u2006'))
- .or(inRange('\u2008', '\u200a'));
-
- /**
- * Determines whether a character is ASCII, meaning that its code point is
- * less than 128.
- */
- public static final CharMatcher ASCII = inRange('\0', '\u007f');
-
- /**
- * Determines whether a character is a digit according to
- * <a href="http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5Cp%7Bdigit%7D">Unicode</a>.
- */
- public static final CharMatcher DIGIT;
-
- static {
- CharMatcher digit = inRange('0', '9');
- String zeroes =
- "\u0660\u06f0\u07c0\u0966\u09e6\u0a66\u0ae6\u0b66\u0be6\u0c66"
- + "\u0ce6\u0d66\u0e50\u0ed0\u0f20\u1040\u1090\u17e0\u1810\u1946"
- + "\u19d0\u1b50\u1bb0\u1c40\u1c50\ua620\ua8d0\ua900\uaa50\uff10";
- for (char base : zeroes.toCharArray()) {
- digit = digit.or(inRange(base, (char) (base + 9)));
- }
- DIGIT = digit;
- }
-
- /**
- * Determines whether a character is whitespace according to {@link
- * Character#isWhitespace(char) Java's definition}; it is usually preferable
- * to use {@link #WHITESPACE}. See a comparison of several definitions of
- * "whitespace" at <a href="http://go/white+space">go/white+space</a>.
- */
- public static final CharMatcher JAVA_WHITESPACE
- = inRange('\u0009', (char) 13) // \\u000d doesn't work as a char literal
- .or(inRange('\u001c', '\u0020'))
- .or(is('\u1680'))
- .or(is('\u180e'))
- .or(inRange('\u2000', '\u2006'))
- .or(inRange('\u2008', '\u200b'))
- .or(inRange('\u2028', '\u2029'))
- .or(is('\u205f'))
- .or(is('\u3000'));
-
- /**
- * Determines whether a character is a digit according to {@link
- * Character#isDigit(char) Java's definition}. If you only care to match
- * ASCII digits, you can use {@code inRange('0', '9')}.
- */
- public static final CharMatcher JAVA_DIGIT = new CharMatcher() {
- @Override public boolean matches(char c) {
- return Character.isDigit(c);
- }
- };
-
- /**
- * Determines whether a character is a letter according to {@link
- * Character#isLetter(char) Java's definition}. If you only care to match
- * letters of the Latin alphabet, you can use {@code
- * inRange('a', 'z').or(inRange('A', 'Z'))}.
- */
- public static final CharMatcher JAVA_LETTER = new CharMatcher() {
- @Override public boolean matches(char c) {
- return Character.isLetter(c);
- }
- };
-
- /**
- * Determines whether a character is a letter or digit according to {@link
- * Character#isLetterOrDigit(char) Java's definition}.
- */
- public static final CharMatcher JAVA_LETTER_OR_DIGIT = new CharMatcher() {
- @Override public boolean matches(char c) {
- return Character.isLetterOrDigit(c);
- }
- };
-
- /**
- * Determines whether a character is upper case according to {@link
- * Character#isUpperCase(char) Java's definition}.
- */
- public static final CharMatcher JAVA_UPPER_CASE = new CharMatcher() {
- @Override public boolean matches(char c) {
- return Character.isUpperCase(c);
- }
- };
-
- /**
- * Determines whether a character is lower case according to {@link
- * Character#isLowerCase(char) Java's definition}.
- */
- public static final CharMatcher JAVA_LOWER_CASE = new CharMatcher() {
- @Override public boolean matches(char c) {
- return Character.isLowerCase(c);
- }
- };
-
- /**
- * Determines whether a character is an ISO control character according to
- * {@link Character#isISOControl(char)}.
- */
- public static final CharMatcher JAVA_ISO_CONTROL = inRange('\u0000', '\u001f')
- .or(inRange('\u007f', '\u009f'));
-
- /**
- * Determines whether a character is invisible; that is, if its Unicode
- * category is any of SPACE_SEPARATOR, LINE_SEPARATOR,
- * PARAGRAPH_SEPARATOR, CONTROL, FORMAT, SURROGATE, and PRIVATE_USE according
- * to ICU4J.
- */
- public static final CharMatcher INVISIBLE = inRange('\u0000', '\u0020')
- .or(inRange('\u007f', '\u00a0'))
- .or(is('\u00ad'))
- .or(inRange('\u0600', '\u0603'))
- .or(anyOf("\u06dd\u070f\u1680\u17b4\u17b5\u180e"))
- .or(inRange('\u2000', '\u200f'))
- .or(inRange('\u2028', '\u202f'))
- .or(inRange('\u205f', '\u2064'))
- .or(inRange('\u206a', '\u206f'))
- .or(is('\u3000'))
- .or(inRange('\ud800', '\uf8ff'))
- .or(anyOf("\ufeff\ufff9\ufffa\ufffb"));
-
- /**
- * Determines whether a character is single-width (not double-width). When
- * in doubt, this matcher errs on the side of returning {@code false} (that
- * is, it tends to assume a character is double-width).
- *
- * <b>Note:</b> as the reference file evolves, we will modify this constant
- * to keep it up to date.
- */
- public static final CharMatcher SINGLE_WIDTH = inRange('\u0000', '\u04f9')
- .or(is('\u05be'))
- .or(inRange('\u05d0', '\u05ea'))
- .or(is('\u05f3'))
- .or(is('\u05f4'))
- .or(inRange('\u0600', '\u06ff'))
- .or(inRange('\u0750', '\u077f'))
- .or(inRange('\u0e00', '\u0e7f'))
- .or(inRange('\u1e00', '\u20af'))
- .or(inRange('\u2100', '\u213a'))
- .or(inRange('\ufb50', '\ufdff'))
- .or(inRange('\ufe70', '\ufeff'))
- .or(inRange('\uff61', '\uffdc'));
-
- /**
- * Determines whether a character is whitespace according to an arbitrary definition used by
- * {@link StringUtil} for years. Most likely you don't want to use this. See a comparison of
- * several definitions of "whitespace" at <a href="http://goto/white space">goto/white space</a>.
- *
- * <p><b>To be deprecated.</b> use {@link #WHITESPACE} to switch to the Unicode definition, or
- * create a matcher for the specific characters you want. Not deprecated yet because it is a
- * stepping stone for getting off of many deprecated {@link StringUtil} methods.
- */
- @Deprecated
- public static final CharMatcher LEGACY_WHITESPACE =
- anyOf(" \r\n\t\u3000\u00A0\u2007\u202F").precomputed();
-
-
- /** Matches any character. */
- public static final CharMatcher ANY = new CharMatcher() {
- @Override public boolean matches(char c) {
- return true;
- }
-
- @Override public int indexIn(CharSequence sequence) {
- return (sequence.length() == 0) ? -1 : 0;
- }
- @Override public int indexIn(CharSequence sequence, int start) {
- int length = sequence.length();
- Preconditions.checkPositionIndex(start, length);
- return (start == length) ? -1 : start;
- }
- @Override public int lastIndexIn(CharSequence sequence) {
- return sequence.length() - 1;
- }
- @Override public boolean matchesAllOf(CharSequence sequence) {
- checkNotNull(sequence);
- return true;
- }
- @Override public boolean matchesNoneOf(CharSequence sequence) {
- return sequence.length() == 0;
- }
- @Override public String removeFrom(CharSequence sequence) {
- checkNotNull(sequence);
- return "";
- }
- @Override public String replaceFrom(
- CharSequence sequence, char replacement) {
- char[] array = new char[sequence.length()];
- Arrays.fill(array, replacement);
- return new String(array);
- }
- @Override public String replaceFrom(
- CharSequence sequence, CharSequence replacement) {
- StringBuilder retval = new StringBuilder(sequence.length() * replacement.length());
- for (int i = 0; i < sequence.length(); i++) {
- retval.append(replacement);
- }
- return retval.toString();
- }
- @Override public String collapseFrom(CharSequence sequence, char replacement) {
- return (sequence.length() == 0) ? "" : String.valueOf(replacement);
- }
- @Override public String trimFrom(CharSequence sequence) {
- checkNotNull(sequence);
- return "";
- }
- @Override public int countIn(CharSequence sequence) {
- return sequence.length();
- }
- @Override public CharMatcher and(CharMatcher other) {
- return checkNotNull(other);
- }
- @Override public CharMatcher or(CharMatcher other) {
- checkNotNull(other);
- return this;
- }
- @Override public CharMatcher negate() {
- return NONE;
- }
- @Override public CharMatcher precomputed() {
- return this;
- }
- };
-
- /** Matches no characters. */
- public static final CharMatcher NONE = new CharMatcher() {
- @Override public boolean matches(char c) {
- return false;
- }
-
- @Override public int indexIn(CharSequence sequence) {
- checkNotNull(sequence);
- return -1;
- }
- @Override public int indexIn(CharSequence sequence, int start) {
- int length = sequence.length();
- Preconditions.checkPositionIndex(start, length);
- return -1;
- }
- @Override public int lastIndexIn(CharSequence sequence) {
- checkNotNull(sequence);
- return -1;
- }
- @Override public boolean matchesAllOf(CharSequence sequence) {
- return sequence.length() == 0;
- }
- @Override public boolean matchesNoneOf(CharSequence sequence) {
- checkNotNull(sequence);
- return true;
- }
- @Override public String removeFrom(CharSequence sequence) {
- return sequence.toString();
- }
- @Override public String replaceFrom(
- CharSequence sequence, char replacement) {
- return sequence.toString();
- }
- @Override public String replaceFrom(
- CharSequence sequence, CharSequence replacement) {
- checkNotNull(replacement);
- return sequence.toString();
- }
- @Override public String collapseFrom(
- CharSequence sequence, char replacement) {
- return sequence.toString();
- }
- @Override public String trimFrom(CharSequence sequence) {
- return sequence.toString();
- }
- @Override public int countIn(CharSequence sequence) {
- checkNotNull(sequence);
- return 0;
- }
- @Override public CharMatcher and(CharMatcher other) {
- checkNotNull(other);
- return this;
- }
- @Override public CharMatcher or(CharMatcher other) {
- return checkNotNull(other);
- }
- @Override public CharMatcher negate() {
- return ANY;
- }
- @Override protected void setBits(LookupTable table) {
- }
- @Override public CharMatcher precomputed() {
- return this;
- }
- };
-
- // Static factories
-
- /**
- * Returns a {@code char} matcher that matches only one specified character.
- */
- public static CharMatcher is(final char match) {
- return new CharMatcher() {
- @Override public boolean matches(char c) {
- return c == match;
- }
-
- @Override public String replaceFrom(
- CharSequence sequence, char replacement) {
- return sequence.toString().replace(match, replacement);
- }
- @Override public CharMatcher and(CharMatcher other) {
- return other.matches(match) ? this : NONE;
- }
- @Override public CharMatcher or(CharMatcher other) {
- return other.matches(match) ? other : super.or(other);
- }
- @Override public CharMatcher negate() {
- return isNot(match);
- }
- @Override protected void setBits(LookupTable table) {
- table.set(match);
- }
- @Override public CharMatcher precomputed() {
- return this;
- }
- };
- }
-
- /**
- * Returns a {@code char} matcher that matches any character except the one
- * specified.
- *
- * <p>To negate another {@code CharMatcher}, use {@link #negate()}.
- */
- public static CharMatcher isNot(final char match) {
- return new CharMatcher() {
- @Override public boolean matches(char c) {
- return c != match;
- }
-
- @Override public CharMatcher and(CharMatcher other) {
- return other.matches(match) ? super.and(other) : other;
- }
- @Override public CharMatcher or(CharMatcher other) {
- return other.matches(match) ? ANY : this;
- }
- @Override public CharMatcher negate() {
- return is(match);
- }
- };
- }
-
- /**
- * Returns a {@code char} matcher that matches any character present in the
- * given character sequence.
- */
- public static CharMatcher anyOf(final CharSequence sequence) {
- switch (sequence.length()) {
- case 0:
- return NONE;
- case 1:
- return is(sequence.charAt(0));
- case 2:
- final char match1 = sequence.charAt(0);
- final char match2 = sequence.charAt(1);
- return new CharMatcher() {
- @Override public boolean matches(char c) {
- return c == match1 || c == match2;
- }
- @Override protected void setBits(LookupTable table) {
- table.set(match1);
- table.set(match2);
- }
- @Override public CharMatcher precomputed() {
- return this;
- }
- };
- }
-
- final char[] chars = sequence.toString().toCharArray();
- Arrays.sort(chars); // not worth collapsing duplicates
-
- return new CharMatcher() {
- @Override public boolean matches(char c) {
- return Arrays.binarySearch(chars, c) >= 0;
- }
- @Override protected void setBits(LookupTable table) {
- for (char c : chars) {
- table.set(c);
- }
- }
- };
- }
-
- /**
- * Returns a {@code char} matcher that matches any character not present in
- * the given character sequence.
- */
- public static CharMatcher noneOf(CharSequence sequence) {
- return anyOf(sequence).negate();
- }
-
- /**
- * Returns a {@code char} matcher that matches any character in a given range
- * (both endpoints are inclusive). For example, to match any lowercase letter
- * of the English alphabet, use {@code CharMatcher.inRange('a', 'z')}.
- *
- * @throws IllegalArgumentException if {@code endInclusive < startInclusive}
- */
- public static CharMatcher inRange(
- final char startInclusive, final char endInclusive) {
- checkArgument(endInclusive >= startInclusive);
- return new CharMatcher() {
- @Override public boolean matches(char c) {
- return startInclusive <= c && c <= endInclusive;
- }
- @Override protected void setBits(LookupTable table) {
- char c = startInclusive;
- while (true) {
- table.set(c);
- if (c++ == endInclusive) {
- break;
- }
- }
- }
- @Override public CharMatcher precomputed() {
- return this;
- }
- };
- }
-
- /**
- * Returns a matcher with identical behavior to the given {@link
- * Character}-based predicate, but which operates on primitive {@code char}
- * instances instead.
- */
- public static CharMatcher forPredicate(
- final Predicate<? super Character> predicate) {
- checkNotNull(predicate);
- if (predicate instanceof CharMatcher) {
- return (CharMatcher) predicate;
- }
- return new CharMatcher() {
- @Override public boolean matches(char c) {
- return predicate.apply(c);
- }
- @Override public boolean apply(Character character) {
- return predicate.apply(checkNotNull(character));
- }
- };
- }
-
- // Abstract methods
-
- /** Determines a true or false value for the given character. */
- public abstract boolean matches(char c);
-
- // Non-static factories
-
- /**
- * Returns a matcher that matches any character not matched by this matcher.
- */
- public CharMatcher negate() {
- final CharMatcher original = this;
- return new CharMatcher() {
- @Override public boolean matches(char c) {
- return !original.matches(c);
- }
-
- @Override public boolean matchesAllOf(CharSequence sequence) {
- return original.matchesNoneOf(sequence);
- }
- @Override public boolean matchesNoneOf(CharSequence sequence) {
- return original.matchesAllOf(sequence);
- }
- @Override public int countIn(CharSequence sequence) {
- return sequence.length() - original.countIn(sequence);
- }
- @Override public CharMatcher negate() {
- return original;
- }
- };
- }
-
- /**
- * Returns a matcher that matches any character matched by both this matcher
- * and {@code other}.
- */
- public CharMatcher and(CharMatcher other) {
- return new And(Arrays.asList(this, checkNotNull(other)));
- }
-
- private static class And extends CharMatcher {
- List<CharMatcher> components;
-
- And(List<CharMatcher> components) {
- this.components = components; // Skip defensive copy (private)
- }
-
- @Override public boolean matches(char c) {
- for (CharMatcher matcher : components) {
- if (!matcher.matches(c)) {
- return false;
- }
- }
- return true;
- }
-
- @Override public CharMatcher and(CharMatcher other) {
- List<CharMatcher> newComponents = new ArrayList<CharMatcher>(components);
- newComponents.add(checkNotNull(other));
- return new And(newComponents);
- }
- }
-
- /**
- * Returns a matcher that matches any character matched by either this matcher
- * or {@code other}.
- */
- public CharMatcher or(CharMatcher other) {
- return new Or(Arrays.asList(this, checkNotNull(other)));
- }
-
- private static class Or extends CharMatcher {
- List<CharMatcher> components;
-
- Or(List<CharMatcher> components) {
- this.components = components; // Skip defensive copy (private)
- }
-
- @Override public boolean matches(char c) {
- for (CharMatcher matcher : components) {
- if (matcher.matches(c)) {
- return true;
- }
- }
- return false;
- }
-
- @Override public CharMatcher or(CharMatcher other) {
- List<CharMatcher> newComponents = new ArrayList<CharMatcher>(components);
- newComponents.add(checkNotNull(other));
- return new Or(newComponents);
- }
-
- @Override protected void setBits(LookupTable table) {
- for (CharMatcher matcher : components) {
- matcher.setBits(table);
- }
- }
- }
-
- /**
- * Returns a {@code char} matcher functionally equivalent to this one, but
- * which may be faster to query than the original; your mileage may vary.
- * Precomputation takes time and is likely to be worthwhile only if the
- * precomputed matcher is queried many thousands of times.
- *
- * <p>This method has no effect (returns {@code this}) when called in GWT:
- * it's unclear whether a precomputed matcher is faster, but it certainly
- * consumes more memory, which doesn't seem like a worthwhile tradeoff in a
- * browser.
- */
- public CharMatcher precomputed() {
- return Platform.precomputeCharMatcher(this);
- }
-
- /**
- * This is the actual implementation of {@link #precomputed}, but we bounce
- * calls through a method on {@link Platform} so that we can have different
- * behavior in GWT.
- *
- * <p>The default precomputation is to cache the configuration of the original
- * matcher in an eight-kilobyte bit array. In some situations this produces a
- * matcher which is faster to query than the original.
- *
- * <p>The default implementation creates a new bit array and passes it to
- * {@link #setBits(LookupTable)}.
- */
- CharMatcher precomputedInternal() {
- final LookupTable table = new LookupTable();
- setBits(table);
-
- return new CharMatcher() {
- @Override public boolean matches(char c) {
- return table.get(c);
- }
-
- // TODO: make methods like negate() smart
-
- @Override public CharMatcher precomputed() {
- return this;
- }
- };
- }
-
- /**
- * For use by implementors; sets the bit corresponding to each character ('\0'
- * to '{@literal \}uFFFF') that matches this matcher in the given bit array,
- * leaving all other bits untouched.
- *
- * <p>The default implementation loops over every possible character value,
- * invoking {@link #matches} for each one.
- */
- protected void setBits(LookupTable table) {
- char c = Character.MIN_VALUE;
- while (true) {
- if (matches(c)) {
- table.set(c);
- }
- if (c++ == Character.MAX_VALUE) {
- break;
- }
- }
- }
-
- /**
- * A bit array with one bit per {@code char} value, used by {@link
- * CharMatcher#precomputed}.
- *
- * <p>TODO: possibly share a common BitArray class with BloomFilter
- * and others... a simpler java.util.BitSet.
- */
- protected static class LookupTable {
- int[] data = new int[2048];
-
- void set(char index) {
- data[index >> 5] |= (1 << index);
- }
- boolean get(char index) {
- return (data[index >> 5] & (1 << index)) != 0;
- }
- }
-
- // Text processing routines
-
- /**
- * Returns {@code true} if a character sequence contains only matching
- * characters.
- *
- * <p>The default implementation iterates over the sequence, invoking {@link
- * #matches} for each character, until this returns {@code false} or the end
- * is reached.
- *
- * @param sequence the character sequence to examine, possibly empty
- * @return {@code true} if this matcher matches every character in the
- * sequence, including when the sequence is empty
- */
- public boolean matchesAllOf(CharSequence sequence) {
- for (int i = sequence.length() - 1; i >= 0; i--) {
- if (!matches(sequence.charAt(i))) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns {@code true} if a character sequence contains no matching
- * characters.
- *
- * <p>The default implementation iterates over the sequence, invoking {@link
- * #matches} for each character, until this returns {@code false} or the end is
- * reached.
- *
- * @param sequence the character sequence to examine, possibly empty
- * @return {@code true} if this matcher matches every character in the
- * sequence, including when the sequence is empty
- */
- public boolean matchesNoneOf(CharSequence sequence) {
- return indexIn(sequence) == -1;
- }
-
- // TODO: perhaps add matchesAnyOf()
-
- /**
- * Returns the index of the first matching character in a character sequence,
- * or {@code -1} if no matching character is present.
- *
- * <p>The default implementation iterates over the sequence in forward order
- * calling {@link #matches} for each character.
- *
- * @param sequence the character sequence to examine from the beginning
- * @return an index, or {@code -1} if no character matches
- */
- public int indexIn(CharSequence sequence) {
- int length = sequence.length();
- for (int i = 0; i < length; i++) {
- if (matches(sequence.charAt(i))) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Returns the index of the first matching character in a character sequence,
- * starting from a given position, or {@code -1} if no character matches after
- * that position.
- *
- * <p>The default implementation iterates over the sequence in forward order,
- * beginning at {@code start}, calling {@link #matches} for each character.
- *
- * @param sequence the character sequence to examine
- * @param start the first index to examine; must be nonnegative and no
- * greater than {@code sequence.length()}
- * @return the index of the first matching character, guaranteed to be no less
- * than {@code start}, or {@code -1} if no character matches
- * @throws IndexOutOfBoundsException if start is negative or greater than
- * {@code sequence.length()}
- */
- public int indexIn(CharSequence sequence, int start) {
- int length = sequence.length();
- Preconditions.checkPositionIndex(start, length);
- for (int i = start; i < length; i++) {
- if (matches(sequence.charAt(i))) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Returns the index of the last matching character in a character sequence,
- * or {@code -1} if no matching character is present.
- *
- * <p>The default implementation iterates over the sequence in reverse order
- * calling {@link #matches} for each character.
- *
- * @param sequence the character sequence to examine from the end
- * @return an index, or {@code -1} if no character matches
- */
- public int lastIndexIn(CharSequence sequence) {
- for (int i = sequence.length() - 1; i >= 0; i--) {
- if (matches(sequence.charAt(i))) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Returns the number of matching characters found in a character sequence.
- */
- public int countIn(CharSequence sequence) {
- int count = 0;
- for (int i = 0; i < sequence.length(); i++) {
- if (matches(sequence.charAt(i))) {
- count++;
- }
- }
- return count;
- }
-
- /**
- * Returns a string containing all non-matching characters of a character
- * sequence, in order. For example: <pre> {@code
- *
- * CharMatcher.is('a').removeFrom("bazaar")}</pre>
- *
- * ... returns {@code "bzr"}.
- */
- public String removeFrom(CharSequence sequence) {
- String string = sequence.toString();
- int pos = indexIn(string);
- if (pos == -1) {
- return string;
- }
-
- char[] chars = string.toCharArray();
- int spread = 1;
-
- // This unusual loop comes from extensive benchmarking
- OUT:
- while (true) {
- pos++;
- while (true) {
- if (pos == chars.length) {
- break OUT;
- }
- if (matches(chars[pos])) {
- break;
- }
- chars[pos - spread] = chars[pos];
- pos++;
- }
- spread++;
- }
- return new String(chars, 0, pos - spread);
- }
-
- /**
- * Returns a string containing all matching characters of a character
- * sequence, in order. For example: <pre> {@code
- *
- * CharMatcher.is('a').retainFrom("bazaar")}</pre>
- *
- * ... returns {@code "aaa"}.
- */
- public String retainFrom(CharSequence sequence) {
- return negate().removeFrom(sequence);
- }
-
- /**
- * Returns a string copy of the input character sequence, with each character
- * that matches this matcher replaced by a given replacement character. For
- * example: <pre> {@code
- *
- * CharMatcher.is('a').replaceFrom("radar", 'o')}</pre>
- *
- * ... returns {@code "rodor"}.
- *
- * <p>The default implementation uses {@link #indexIn(CharSequence)} to find
- * the first matching character, then iterates the remainder of the sequence
- * calling {@link #matches(char)} for each character.
- *
- * @param sequence the character sequence to replace matching characters in
- * @param replacement the character to append to the result string in place of
- * each matching character in {@code sequence}
- * @return the new string
- */
- public String replaceFrom(CharSequence sequence, char replacement) {
- String string = sequence.toString();
- int pos = indexIn(string);
- if (pos == -1) {
- return string;
- }
- char[] chars = string.toCharArray();
- chars[pos] = replacement;
- for (int i = pos + 1; i < chars.length; i++) {
- if (matches(chars[i])) {
- chars[i] = replacement;
- }
- }
- return new String(chars);
- }
-
- /**
- * Returns a string copy of the input character sequence, with each character
- * that matches this matcher replaced by a given replacement sequence. For
- * example: <pre> {@code
- *
- * CharMatcher.is('a').replaceFrom("yaha", "oo")}</pre>
- *
- * ... returns {@code "yoohoo"}.
- *
- * <p><b>Note:</b> If the replacement is a fixed string with only one character,
- * you are better off calling {@link #replaceFrom(CharSequence, char)} directly.
- *
- * @param sequence the character sequence to replace matching characters in
- * @param replacement the characters to append to the result string in place
- * of each matching character in {@code sequence}
- * @return the new string
- */
- public String replaceFrom(CharSequence sequence, CharSequence replacement) {
- int replacementLen = replacement.length();
- if (replacementLen == 0) {
- return removeFrom(sequence);
- }
- if (replacementLen == 1) {
- return replaceFrom(sequence, replacement.charAt(0));
- }
-
- String string = sequence.toString();
- int pos = indexIn(string);
- if (pos == -1) {
- return string;
- }
-
- int len = string.length();
- StringBuilder buf = new StringBuilder((int) (len * 1.5) + 16);
-
- int oldpos = 0;
- do {
- buf.append(string, oldpos, pos);
- buf.append(replacement);
- oldpos = pos + 1;
- pos = indexIn(string, oldpos);
- } while (pos != -1);
-
- buf.append(string, oldpos, len);
- return buf.toString();
- }
-
- /**
- * Returns a substring of the input character sequence that omits all
- * characters this matcher matches from the beginning and from the end of the
- * string. For example: <pre> {@code
- *
- * CharMatcher.anyOf("ab").trimFrom("abacatbab")}</pre>
- *
- * ... returns {@code "cat"}.
- *
- * <p>Note that<pre> {@code
- *
- * CharMatcher.inRange('\0', ' ').trimFrom(str)}</pre>
- *
- * ... is equivalent to {@link String#trim()}.
- */
- public String trimFrom(CharSequence sequence) {
- int len = sequence.length();
- int first;
- int last;
-
- for (first = 0; first < len; first++) {
- if (!matches(sequence.charAt(first))) {
- break;
- }
- }
- for (last = len - 1; last > first; last--) {
- if (!matches(sequence.charAt(last))) {
- break;
- }
- }
-
- return sequence.subSequence(first, last + 1).toString();
- }
-
- /**
- * Returns a substring of the input character sequence that omits all
- * characters this matcher matches from the beginning of the
- * string. For example: <pre> {@code
- *
- * CharMatcher.anyOf("ab").trimLeadingFrom("abacatbab")}</pre>
- *
- * ... returns {@code "catbab"}.
- */
- public String trimLeadingFrom(CharSequence sequence) {
- int len = sequence.length();
- int first;
-
- for (first = 0; first < len; first++) {
- if (!matches(sequence.charAt(first))) {
- break;
- }
- }
-
- return sequence.subSequence(first, len).toString();
- }
-
- /**
- * Returns a substring of the input character sequence that omits all
- * characters this matcher matches from the end of the
- * string. For example: <pre> {@code
- *
- * CharMatcher.anyOf("ab").trimTrailingFrom("abacatbab")}</pre>
- *
- * ... returns {@code "abacat"}.
- */
- public String trimTrailingFrom(CharSequence sequence) {
- int len = sequence.length();
- int last;
-
- for (last = len - 1; last >= 0; last--) {
- if (!matches(sequence.charAt(last))) {
- break;
- }
- }
-
- return sequence.subSequence(0, last + 1).toString();
- }
-
- /**
- * Returns a string copy of the input character sequence, with each group of
- * consecutive characters that match this matcher replaced by a single
- * replacement character. For example: <pre> {@code
- *
- * CharMatcher.anyOf("eko").collapseFrom("bookkeeper", '-')}</pre>
- *
- * ... returns {@code "b-p-r"}.
- *
- * <p>The default implementation uses {@link #indexIn(CharSequence)} to find
- * the first matching character, then iterates the remainder of the sequence
- * calling {@link #matches(char)} for each character.
- *
- * @param sequence the character sequence to replace matching groups of
- * characters in
- * @param replacement the character to append to the result string in place of
- * each group of matching characters in {@code sequence}
- * @return the new string
- */
- public String collapseFrom(CharSequence sequence, char replacement) {
- int first = indexIn(sequence);
- if (first == -1) {
- return sequence.toString();
- }
-
- // TODO: this implementation can probably be made faster.
-
- StringBuilder builder = new StringBuilder(sequence.length())
- .append(sequence.subSequence(0, first))
- .append(replacement);
- boolean in = true;
- for (int i = first + 1; i < sequence.length(); i++) {
- char c = sequence.charAt(i);
- if (apply(c)) {
- if (!in) {
- builder.append(replacement);
- in = true;
- }
- } else {
- builder.append(c);
- in = false;
- }
- }
- return builder.toString();
- }
-
- /**
- * Collapses groups of matching characters exactly as {@link #collapseFrom}
- * does, except that groups of matching characters at the start or end of the
- * sequence are removed without replacement.
- */
- public String trimAndCollapseFrom(CharSequence sequence, char replacement) {
- int first = negate().indexIn(sequence);
- if (first == -1) {
- return ""; // everything matches. nothing's left.
- }
- StringBuilder builder = new StringBuilder(sequence.length());
- boolean inMatchingGroup = false;
- for (int i = first; i < sequence.length(); i++) {
- char c = sequence.charAt(i);
- if (apply(c)) {
- inMatchingGroup = true;
- } else {
- if (inMatchingGroup) {
- builder.append(replacement);
- inMatchingGroup = false;
- }
- builder.append(c);
- }
- }
- return builder.toString();
- }
-
- // Predicate interface
-
- /**
- * Returns {@code true} if this matcher matches the given character.
- *
- * @throws NullPointerException if {@code character} is null
- */
- /*@Override*/ public boolean apply(Character character) {
- return matches(character);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/Escaper.java b/src/com/android/mail/lib/base/Escaper.java
deleted file mode 100644
index 9f6b8f0..0000000
--- a/src/com/android/mail/lib/base/Escaper.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2008 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.mail.lib.base;
-
-/**
- * An object that converts literal text into a format safe for inclusion in a particular context
- * (such as an XML document). Typically (but not always), the inverse process of "unescaping" the
- * text is performed automatically by the relevant parser.
- *
- * <p>For example, an XML escaper would convert the literal string {@code "Foo<Bar>"} into {@code
- * "Foo<Bar>"} to prevent {@code "<Bar>"} from being confused with an XML tag. When the
- * resulting XML document is parsed, the parser API will return this text as the original literal
- * string {@code "Foo<Bar>"}.
- *
- * <p>An {@code Escaper} instance is required to be stateless, and safe when used concurrently by
- * multiple threads.
- *
- * <p>The two primary implementations of this interface are {@link CharEscaper} and {@link
- * UnicodeEscaper}. They are heavily optimized for performance and greatly simplify the task of
- * implementing new escapers. It is strongly recommended that when implementing a new escaper you
- * extend one of these classes. If you find that you are unable to achieve the desired behavior
- * using either of these classes, please contact the Java libraries team for advice.
- *
- * <p>Several popular escapers are defined as constants in the class {@link CharEscapers}. To create
- * your own escapers, use {@link CharEscaperBuilder}, or extend {@link CharEscaper} or {@code
- * UnicodeEscaper}.
- *
- * @author dbeaumont@google.com (David Beaumont)
- */
-public abstract class Escaper {
- /**
- * Returns the escaped form of a given literal string.
- *
- * <p>Note that this method may treat input characters differently depending on the specific
- * escaper implementation.
- *
- * <ul>
- * <li>{@link UnicodeEscaper} handles <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a>
- * correctly, including surrogate character pairs. If the input is badly formed the escaper
- * should throw {@link IllegalArgumentException}.
- * <li>{@link CharEscaper} handles Java characters independently and does not verify the input
- * for well formed characters. A CharEscaper should not be used in situations where input is
- * not guaranteed to be restricted to the Basic Multilingual Plane (BMP).
- * </ul>
- *
- * @param string the literal string to be escaped
- * @return the escaped form of {@code string}
- * @throws NullPointerException if {@code string} is null
- * @throws IllegalArgumentException if {@code string} contains badly formed UTF-16 or cannot be
- * escaped for any other reason
- */
- public abstract String escape(String string);
-
- /**
- * Returns an {@code Appendable} instance which automatically escapes all text appended to it
- * before passing the resulting text to an underlying {@code Appendable}.
- *
- * <p>Note that the Appendable returned by this method may treat input characters differently
- * depending on the specific escaper implementation.
- *
- * <ul>
- * <li>{@link UnicodeEscaper} handles <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a>
- * correctly, including surrogate character pairs. If the input is badly formed the escaper
- * should throw {@link IllegalArgumentException}.
- * <li>{@link CharEscaper} handles Java characters independently and does not verify the input
- * for well formed characters. A CharEscaper should not be used in situations where input is
- * not guaranteed to be restricted to the Basic Multilingual Plane (BMP).
- * </ul>
- *
- * <p>In all implementations the escaped Appendable should throw {@code NullPointerException} if
- * given a {@code null} {@link CharSequence}.
- *
- * @param out the underlying {@code Appendable} to append escaped output to
- * @return an {@code Appendable} which passes text to {@code out} after escaping it
- */
- public abstract Appendable escape(Appendable out);
-
- private final Function<String, String> asFunction =
- new Function<String, String>() {
- public String apply(String from) {
- return escape(from);
- }
- };
-
- /**
- * Returns a {@link Function} that invokes {@link #escape(String)} on this escaper.
- */
- public Function<String, String> asFunction() {
- return asFunction;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/Function.java b/src/com/android/mail/lib/base/Function.java
deleted file mode 100644
index 6df8289..0000000
--- a/src/com/android/mail/lib/base/Function.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2007 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.mail.lib.base;
-
-/**
- * A transformation from one object to another. For example, a
- * {@code StringToIntegerFunction} may implement
- * <code>Function<String,Integer></code> and transform integers in
- * {@code String} format to {@code Integer} format.
- *
- * <p>The transformation on the source object does not necessarily result in
- * an object of a different type. For example, a
- * {@code FarenheitToCelsiusFunction} may implement
- * <code>Function<Float,Float></code>.
- *
- * <p>Implementations which may cause side effects upon evaluation are strongly
- * encouraged to state this fact clearly in their API documentation.
- *
- * @param <F> the type of the function input
- * @param <T> the type of the function output
- * @author Kevin Bourrillion
- * @author Scott Bonneau
- * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library)
- */
-public interface Function<F, T> {
-
- /**
- * Applies the function to an object of type {@code F}, resulting in an object
- * of type {@code T}. Note that types {@code F} and {@code T} may or may not
- * be the same.
- *
- * @param from the source object
- * @return the resulting object
- */
- T apply(F from);
-
- /**
- * Indicates whether some other object is equal to this {@code Function}.
- * This method can return {@code true} <i>only</i> if the specified object is
- * also a {@code Function} and, for every input object {@code o}, it returns
- * exactly the same value. Thus, {@code function1.equals(function2)} implies
- * that either {@code function1.apply(o)} and {@code function2.apply(o)} are
- * both null, or {@code function1.apply(o).equals(function2.apply(o))}.
- *
- * <p>Note that it is always safe <em>not</em> to override
- * {@link Object#equals}.
- */
- boolean equals(Object obj);
-}
diff --git a/src/com/android/mail/lib/base/PercentEscaper.java b/src/com/android/mail/lib/base/PercentEscaper.java
deleted file mode 100644
index e7baa36..0000000
--- a/src/com/android/mail/lib/base/PercentEscaper.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2008 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.mail.lib.base;
-
-import static com.android.mail.lib.base.Preconditions.checkNotNull;
-
-/**
- * A {@code UnicodeEscaper} that escapes some set of Java characters using
- * the URI percent encoding scheme. The set of safe characters (those which
- * remain unescaped) can be specified on construction.
- *
- * <p>For details on escaping URIs for use in web pages, see section 2.4 of
- * <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
- *
- * <p>In most cases this class should not need to be used directly. If you
- * have no special requirements for escaping your URIs, you should use either
- * {@link CharEscapers#uriEscaper()} or
- * {@link CharEscapers#uriEscaper(boolean)}.
- *
- * <p>When encoding a String, the following rules apply:
- * <ul>
- * <li>The alphanumeric characters "a" through "z", "A" through "Z" and "0"
- * through "9" remain the same.
- * <li>Any additionally specified safe characters remain the same.
- * <li>If {@code plusForSpace} was specified, the space character " " is
- * converted into a plus sign "+".
- * <li>All other characters are converted into one or more bytes using UTF-8
- * encoding and each byte is then represented by the 3-character string
- * "%XY", where "XY" is the two-digit, uppercase, hexadecimal representation
- * of the byte value.
- * </ul>
- *
- * <p>RFC 2396 specifies the set of unreserved characters as "-", "_", ".", "!",
- * "~", "*", "'", "(" and ")". It goes on to state:
- *
- * <p><i>Unreserved characters can be escaped without changing the semantics
- * of the URI, but this should not be done unless the URI is being used
- * in a context that does not allow the unescaped character to appear.</i>
- *
- * <p>For performance reasons the only currently supported character encoding of
- * this class is UTF-8.
- *
- * <p><b>Note</b>: This escaper produces uppercase hexadecimal sequences. From
- * <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>:<br>
- * <i>"URI producers and normalizers should use uppercase hexadecimal digits
- * for all percent-encodings."</i>
- *
- * @author dbeaumont@google.com (David Beaumont)
- */
-public class PercentEscaper extends UnicodeEscaper {
- /**
- * A string of safe characters that mimics the behavior of
- * {@link java.net.URLEncoder}.
- *
- * TODO(dbeaumont): Fix escapers to be compliant with RFC 3986
- */
- public static final String SAFECHARS_URLENCODER = "-_.*";
-
- /**
- * A string of characters that do not need to be encoded when used in URI
- * path segments, as specified in RFC 3986. Note that some of these
- * characters do need to be escaped when used in other parts of the URI.
- */
- public static final String SAFEPATHCHARS_URLENCODER = "-_.!~*'()@:$&,;=";
-
- /**
- * A string of characters that do not need to be encoded when used in URI
- * query strings, as specified in RFC 3986. Note that some of these
- * characters do need to be escaped when used in other parts of the URI.
- */
- public static final String SAFEQUERYSTRINGCHARS_URLENCODER
- = "-_.!~*'()@:$,;/?:";
-
- // In some uri escapers spaces are escaped to '+'
- private static final char[] URI_ESCAPED_SPACE = { '+' };
-
- // TODO(dbeaumont): Remove this once UriEscaper uses lower case
- private static final char[] UPPER_HEX_DIGITS =
- "0123456789ABCDEF".toCharArray();
-
- /**
- * If true we should convert space to the {@code +} character.
- */
- private final boolean plusForSpace;
-
- /**
- * An array of flags where for any {@code char c} if {@code safeOctets[c]} is
- * true then {@code c} should remain unmodified in the output. If
- * {@code c > safeOctets.length} then it should be escaped.
- */
- private final boolean[] safeOctets;
-
- /**
- * Constructs a URI escaper with the specified safe characters and optional
- * handling of the space character.
- *
- * @param safeChars a non null string specifying additional safe characters
- * for this escaper (the ranges 0..9, a..z and A..Z are always safe and
- * should not be specified here)
- * @param plusForSpace true if ASCII space should be escaped to {@code +}
- * rather than {@code %20}
- * @throws IllegalArgumentException if any of the parameters were invalid
- */
- public PercentEscaper(String safeChars, boolean plusForSpace) {
- checkNotNull(safeChars); // eager for GWT.
-
- // Avoid any misunderstandings about the behavior of this escaper
- if (safeChars.matches(".*[0-9A-Za-z].*")) {
- throw new IllegalArgumentException(
- "Alphanumeric characters are always 'safe' and should not be " +
- "explicitly specified");
- }
- // Avoid ambiguous parameters. Safe characters are never modified so if
- // space is a safe character then setting plusForSpace is meaningless.
- if (plusForSpace && safeChars.contains(" ")) {
- throw new IllegalArgumentException(
- "plusForSpace cannot be specified when space is a 'safe' character");
- }
- if (safeChars.contains("%")) {
- throw new IllegalArgumentException(
- "The '%' character cannot be specified as 'safe'");
- }
- this.plusForSpace = plusForSpace;
- this.safeOctets = createSafeOctets(safeChars);
- }
-
- /**
- * Creates a boolean[] with entries corresponding to the character values
- * for 0-9, A-Z, a-z and those specified in safeChars set to true. The array
- * is as small as is required to hold the given character information.
- */
- private static boolean[] createSafeOctets(String safeChars) {
- int maxChar = 'z';
- char[] safeCharArray = safeChars.toCharArray();
- for (char c : safeCharArray) {
- maxChar = Math.max(c, maxChar);
- }
- boolean[] octets = new boolean[maxChar + 1];
- for (int c = '0'; c <= '9'; c++) {
- octets[c] = true;
- }
- for (int c = 'A'; c <= 'Z'; c++) {
- octets[c] = true;
- }
- for (int c = 'a'; c <= 'z'; c++) {
- octets[c] = true;
- }
- for (char c : safeCharArray) {
- octets[c] = true;
- }
- return octets;
- }
-
- /*
- * Overridden for performance. For unescaped strings this improved the
- * performance of the uri escaper from ~760ns to ~400ns as measured by
- * {@link CharEscapersBenchmark}.
- */
- @Override
- protected int nextEscapeIndex(CharSequence csq, int index, int end) {
- for (; index < end; index++) {
- char c = csq.charAt(index);
- if (c >= safeOctets.length || !safeOctets[c]) {
- break;
- }
- }
- return index;
- }
-
- /*
- * Overridden for performance. For unescaped strings this improved the
- * performance of the uri escaper from ~400ns to ~170ns as measured by
- * {@link CharEscapersBenchmark}.
- */
- @Override
- public String escape(String s) {
- checkNotNull(s);
- int slen = s.length();
- for (int index = 0; index < slen; index++) {
- char c = s.charAt(index);
- if (c >= safeOctets.length || !safeOctets[c]) {
- return escapeSlow(s, index);
- }
- }
- return s;
- }
-
- /**
- * Escapes the given Unicode code point in UTF-8.
- */
- @Override
- protected char[] escape(int cp) {
- // We should never get negative values here but if we do it will throw an
- // IndexOutOfBoundsException, so at least it will get spotted.
- if (cp < safeOctets.length && safeOctets[cp]) {
- return null;
- } else if (cp == ' ' && plusForSpace) {
- return URI_ESCAPED_SPACE;
- } else if (cp <= 0x7F) {
- // Single byte UTF-8 characters
- // Start with "%--" and fill in the blanks
- char[] dest = new char[3];
- dest[0] = '%';
- dest[2] = UPPER_HEX_DIGITS[cp & 0xF];
- dest[1] = UPPER_HEX_DIGITS[cp >>> 4];
- return dest;
- } else if (cp <= 0x7ff) {
- // Two byte UTF-8 characters [cp >= 0x80 && cp <= 0x7ff]
- // Start with "%--%--" and fill in the blanks
- char[] dest = new char[6];
- dest[0] = '%';
- dest[3] = '%';
- dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[2] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[1] = UPPER_HEX_DIGITS[0xC | cp];
- return dest;
- } else if (cp <= 0xffff) {
- // Three byte UTF-8 characters [cp >= 0x800 && cp <= 0xffff]
- // Start with "%E-%--%--" and fill in the blanks
- char[] dest = new char[9];
- dest[0] = '%';
- dest[1] = 'E';
- dest[3] = '%';
- dest[6] = '%';
- dest[8] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[2] = UPPER_HEX_DIGITS[cp];
- return dest;
- } else if (cp <= 0x10ffff) {
- char[] dest = new char[12];
- // Four byte UTF-8 characters [cp >= 0xffff && cp <= 0x10ffff]
- // Start with "%F-%--%--%--" and fill in the blanks
- dest[0] = '%';
- dest[1] = 'F';
- dest[3] = '%';
- dest[6] = '%';
- dest[9] = '%';
- dest[11] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[10] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[8] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[5] = UPPER_HEX_DIGITS[cp & 0xF];
- cp >>>= 4;
- dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)];
- cp >>>= 2;
- dest[2] = UPPER_HEX_DIGITS[cp & 0x7];
- return dest;
- } else {
- // If this ever happens it is due to bug in UnicodeEscaper, not bad input.
- throw new IllegalArgumentException(
- "Invalid unicode character value " + cp);
- }
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/Platform.java b/src/com/android/mail/lib/base/Platform.java
deleted file mode 100644
index 78e29f3..0000000
--- a/src/com/android/mail/lib/base/Platform.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2009 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.mail.lib.base;
-
-
-/**
- * Methods factored out so that they can be emulated differently in GWT.
- *
- * @author Jesse Wilson
- */
-final class Platform {
- private Platform() {}
-
- /**
- * Calls {@link Class#isInstance(Object)}.
- *
- * <p>This method is not supported in GWT yet.
- */
- static boolean isInstance(Class<?> clazz, Object obj) {
- return clazz.isInstance(obj);
- }
-
- /** Returns a thread-local 1024-char array. */
- static char[] charBufferFromThreadLocal() {
- return DEST_TL.get();
- }
-
- /**
- * A thread-local destination buffer to keep us from creating new buffers.
- * The starting size is 1024 characters. If we grow past this we don't
- * put it back in the threadlocal, we just keep going and grow as needed.
- */
- private static final ThreadLocal<char[]> DEST_TL = new ThreadLocal<char[]>() {
- @Override
- protected char[] initialValue() {
- return new char[1024];
- }
- };
-
- static CharMatcher precomputeCharMatcher(CharMatcher matcher) {
- return matcher.precomputedInternal();
- }
-}
diff --git a/src/com/android/mail/lib/base/Preconditions.java b/src/com/android/mail/lib/base/Preconditions.java
deleted file mode 100644
index ebf661a..0000000
--- a/src/com/android/mail/lib/base/Preconditions.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * Copyright (C) 2007 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.mail.lib.base;
-
-import java.util.NoSuchElementException;
-
-/**
- * Simple static methods to be called at the start of your own methods to verify
- * correct arguments and state. This allows constructs such as
- * <pre>
- * if (count <= 0) {
- * throw new IllegalArgumentException("must be positive: " + count);
- * }</pre>
- *
- * to be replaced with the more compact
- * <pre>
- * checkArgument(count > 0, "must be positive: %s", count);</pre>
- *
- * Note that the sense of the expression is inverted; with {@code Preconditions}
- * you declare what you expect to be <i>true</i>, just as you do with an
- * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/language/assert.html">
- * {@code assert}</a> or a JUnit {@code assertTrue} call.
- *
- * <p><b>Warning:</b> only the {@code "%s"} specifier is recognized as a
- * placeholder in these messages, not the full range of {@link
- * String#format(String, Object[])} specifiers.
- *
- * <p>Take care not to confuse precondition checking with other similar types
- * of checks! Precondition exceptions -- including those provided here, but also
- * {@link IndexOutOfBoundsException}, {@link NoSuchElementException}, {@link
- * UnsupportedOperationException} and others -- are used to signal that the
- * <i>calling method</i> has made an error. This tells the caller that it should
- * not have invoked the method when it did, with the arguments it did, or
- * perhaps ever. Postcondition or other invariant failures should not throw
- * these types of exceptions.
- *
- * @author Kevin Bourrillion
- * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library)
- */
-public class Preconditions {
- private Preconditions() {}
-
- /**
- * Ensures the truth of an expression involving one or more parameters to the
- * calling method.
- *
- * @param expression a boolean expression
- * @throws IllegalArgumentException if {@code expression} is false
- */
- public static void checkArgument(boolean expression) {
- if (!expression) {
- throw new IllegalArgumentException();
- }
- }
-
- /**
- * Ensures the truth of an expression involving one or more parameters to the
- * calling method.
- *
- * @param expression a boolean expression
- * @param errorMessage the exception message to use if the check fails; will
- * be converted to a string using {@link String#valueOf(Object)}
- * @throws IllegalArgumentException if {@code expression} is false
- */
- public static void checkArgument(boolean expression, Object errorMessage) {
- if (!expression) {
- throw new IllegalArgumentException(String.valueOf(errorMessage));
- }
- }
-
- /**
- * Ensures the truth of an expression involving one or more parameters to the
- * calling method.
- *
- * @param expression a boolean expression
- * @param errorMessageTemplate a template for the exception message should the
- * check fail. The message is formed by replacing each {@code %s}
- * placeholder in the template with an argument. These are matched by
- * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
- * Unmatched arguments will be appended to the formatted message in square
- * braces. Unmatched placeholders will be left as-is.
- * @param errorMessageArgs the arguments to be substituted into the message
- * template. Arguments are converted to strings using
- * {@link String#valueOf(Object)}.
- * @throws IllegalArgumentException if {@code expression} is false
- * @throws NullPointerException if the check fails and either {@code
- * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
- * this happen)
- */
- public static void checkArgument(boolean expression,
- String errorMessageTemplate, Object... errorMessageArgs) {
- if (!expression) {
- throw new IllegalArgumentException(
- format(errorMessageTemplate, errorMessageArgs));
- }
- }
-
- /**
- * Ensures the truth of an expression involving the state of the calling
- * instance, but not involving any parameters to the calling method.
- *
- * @param expression a boolean expression
- * @throws IllegalStateException if {@code expression} is false
- */
- public static void checkState(boolean expression) {
- if (!expression) {
- throw new IllegalStateException();
- }
- }
-
- /**
- * Ensures the truth of an expression involving the state of the calling
- * instance, but not involving any parameters to the calling method.
- *
- * @param expression a boolean expression
- * @param errorMessage the exception message to use if the check fails; will
- * be converted to a string using {@link String#valueOf(Object)}
- * @throws IllegalStateException if {@code expression} is false
- */
- public static void checkState(boolean expression, Object errorMessage) {
- if (!expression) {
- throw new IllegalStateException(String.valueOf(errorMessage));
- }
- }
-
- /**
- * Ensures the truth of an expression involving the state of the calling
- * instance, but not involving any parameters to the calling method.
- *
- * @param expression a boolean expression
- * @param errorMessageTemplate a template for the exception message should the
- * check fail. The message is formed by replacing each {@code %s}
- * placeholder in the template with an argument. These are matched by
- * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
- * Unmatched arguments will be appended to the formatted message in square
- * braces. Unmatched placeholders will be left as-is.
- * @param errorMessageArgs the arguments to be substituted into the message
- * template. Arguments are converted to strings using
- * {@link String#valueOf(Object)}.
- * @throws IllegalStateException if {@code expression} is false
- * @throws NullPointerException if the check fails and either {@code
- * errorMessageTemplate} or {@code errorMessageArgs} is null (don't let
- * this happen)
- */
- public static void checkState(boolean expression,
- String errorMessageTemplate, Object... errorMessageArgs) {
- if (!expression) {
- throw new IllegalStateException(
- format(errorMessageTemplate, errorMessageArgs));
- }
- }
-
- /**
- * Ensures that an object reference passed as a parameter to the calling
- * method is not null.
- *
- * @param reference an object reference
- * @return the non-null reference that was validated
- * @throws NullPointerException if {@code reference} is null
- */
- public static <T> T checkNotNull(T reference) {
- if (reference == null) {
- throw new NullPointerException();
- }
- return reference;
- }
-
- /**
- * Ensures that an object reference passed as a parameter to the calling
- * method is not null.
- *
- * @param reference an object reference
- * @param errorMessage the exception message to use if the check fails; will
- * be converted to a string using {@link String#valueOf(Object)}
- * @return the non-null reference that was validated
- * @throws NullPointerException if {@code reference} is null
- */
- public static <T> T checkNotNull(T reference, Object errorMessage) {
- if (reference == null) {
- throw new NullPointerException(String.valueOf(errorMessage));
- }
- return reference;
- }
-
- /**
- * Ensures that an object reference passed as a parameter to the calling
- * method is not null.
- *
- * @param reference an object reference
- * @param errorMessageTemplate a template for the exception message should the
- * check fail. The message is formed by replacing each {@code %s}
- * placeholder in the template with an argument. These are matched by
- * position - the first {@code %s} gets {@code errorMessageArgs[0]}, etc.
- * Unmatched arguments will be appended to the formatted message in square
- * braces. Unmatched placeholders will be left as-is.
- * @param errorMessageArgs the arguments to be substituted into the message
- * template. Arguments are converted to strings using
- * {@link String#valueOf(Object)}.
- * @return the non-null reference that was validated
- * @throws NullPointerException if {@code reference} is null
- */
- public static <T> T checkNotNull(T reference, String errorMessageTemplate,
- Object... errorMessageArgs) {
- if (reference == null) {
- // If either of these parameters is null, the right thing happens anyway
- throw new NullPointerException(
- format(errorMessageTemplate, errorMessageArgs));
- }
- return reference;
- }
-
- /*
- * All recent hotspots (as of 2009) *really* like to have the natural code
- *
- * if (guardExpression) {
- * throw new BadException(messageExpression);
- * }
- *
- * refactored so that messageExpression is moved to a separate
- * String-returning method.
- *
- * if (guardExpression) {
- * throw new BadException(badMsg(...));
- * }
- *
- * The alternative natural refactorings into void or Exception-returning
- * methods are much slower. This is a big deal - we're talking factors of
- * 2-8 in microbenchmarks, not just 10-20%. (This is a hotspot optimizer
- * bug, which should be fixed, but that's a separate, big project).
- *
- * The coding pattern above is heavily used in java.util, e.g. in ArrayList.
- * There is a RangeCheckMicroBenchmark in the JDK that was used to test this.
- *
- * But the methods in this class want to throw different exceptions,
- * depending on the args, so it appears that this pattern is not directly
- * applicable. But we can use the ridiculous, devious trick of throwing an
- * exception in the middle of the construction of another exception.
- * Hotspot is fine with that.
- */
-
- /**
- * Ensures that {@code index} specifies a valid <i>element</i> in an array,
- * list or string of size {@code size}. An element index may range from zero,
- * inclusive, to {@code size}, exclusive.
- *
- * @param index a user-supplied index identifying an element of an array, list
- * or string
- * @param size the size of that array, list or string
- * @return the value of {@code index}
- * @throws IndexOutOfBoundsException if {@code index} is negative or is not
- * less than {@code size}
- * @throws IllegalArgumentException if {@code size} is negative
- */
- public static int checkElementIndex(int index, int size) {
- return checkElementIndex(index, size, "index");
- }
-
- /**
- * Ensures that {@code index} specifies a valid <i>element</i> in an array,
- * list or string of size {@code size}. An element index may range from zero,
- * inclusive, to {@code size}, exclusive.
- *
- * @param index a user-supplied index identifying an element of an array, list
- * or string
- * @param size the size of that array, list or string
- * @param desc the text to use to describe this index in an error message
- * @return the value of {@code index}
- * @throws IndexOutOfBoundsException if {@code index} is negative or is not
- * less than {@code size}
- * @throws IllegalArgumentException if {@code size} is negative
- */
- public static int checkElementIndex(int index, int size, String desc) {
- // Carefully optimized for execution by hotspot (explanatory comment above)
- if (index < 0 || index >= size) {
- throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
- }
- return index;
- }
-
- private static String badElementIndex(int index, int size, String desc) {
- if (index < 0) {
- return format("%s (%s) must not be negative", desc, index);
- } else if (size < 0) {
- throw new IllegalArgumentException("negative size: " + size);
- } else { // index >= size
- return format("%s (%s) must be less than size (%s)", desc, index, size);
- }
- }
-
- /**
- * Ensures that {@code index} specifies a valid <i>position</i> in an array,
- * list or string of size {@code size}. A position index may range from zero
- * to {@code size}, inclusive.
- *
- * @param index a user-supplied index identifying a position in an array, list
- * or string
- * @param size the size of that array, list or string
- * @return the value of {@code index}
- * @throws IndexOutOfBoundsException if {@code index} is negative or is
- * greater than {@code size}
- * @throws IllegalArgumentException if {@code size} is negative
- */
- public static int checkPositionIndex(int index, int size) {
- return checkPositionIndex(index, size, "index");
- }
-
- /**
- * Ensures that {@code index} specifies a valid <i>position</i> in an array,
- * list or string of size {@code size}. A position index may range from zero
- * to {@code size}, inclusive.
- *
- * @param index a user-supplied index identifying a position in an array, list
- * or string
- * @param size the size of that array, list or string
- * @param desc the text to use to describe this index in an error message
- * @return the value of {@code index}
- * @throws IndexOutOfBoundsException if {@code index} is negative or is
- * greater than {@code size}
- * @throws IllegalArgumentException if {@code size} is negative
- */
- public static int checkPositionIndex(int index, int size, String desc) {
- // Carefully optimized for execution by hotspot (explanatory comment above)
- if (index < 0 || index > size) {
- throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
- }
- return index;
- }
-
- private static String badPositionIndex(int index, int size, String desc) {
- if (index < 0) {
- return format("%s (%s) must not be negative", desc, index);
- } else if (size < 0) {
- throw new IllegalArgumentException("negative size: " + size);
- } else { // index > size
- return format("%s (%s) must not be greater than size (%s)",
- desc, index, size);
- }
- }
-
- /**
- * Ensures that {@code start} and {@code end} specify a valid <i>positions</i>
- * in an array, list or string of size {@code size}, and are in order. A
- * position index may range from zero to {@code size}, inclusive.
- *
- * @param start a user-supplied index identifying a starting position in an
- * array, list or string
- * @param end a user-supplied index identifying a ending position in an array,
- * list or string
- * @param size the size of that array, list or string
- * @throws IndexOutOfBoundsException if either index is negative or is
- * greater than {@code size}, or if {@code end} is less than {@code start}
- * @throws IllegalArgumentException if {@code size} is negative
- */
- public static void checkPositionIndexes(int start, int end, int size) {
- // Carefully optimized for execution by hotspot (explanatory comment above)
- if (start < 0 || end < start || end > size) {
- throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
- }
- }
-
- private static String badPositionIndexes(int start, int end, int size) {
- if (start < 0 || start > size) {
- return badPositionIndex(start, size, "start index");
- }
- if (end < 0 || end > size) {
- return badPositionIndex(end, size, "end index");
- }
- // end < start
- return format("end index (%s) must not be less than start index (%s)",
- end, start);
- }
-
- /**
- * Substitutes each {@code %s} in {@code template} with an argument. These
- * are matched by position - the first {@code %s} gets {@code args[0]}, etc.
- * If there are more arguments than placeholders, the unmatched arguments will
- * be appended to the end of the formatted message in square braces.
- *
- * @param template a non-null string containing 0 or more {@code %s}
- * placeholders.
- * @param args the arguments to be substituted into the message
- * template. Arguments are converted to strings using
- * {@link String#valueOf(Object)}. Arguments can be null.
- */
- static String format(String template, Object... args) {
- // start substituting the arguments into the '%s' placeholders
- StringBuilder builder = new StringBuilder(
- template.length() + 16 * args.length);
- int templateStart = 0;
- int i = 0;
- while (i < args.length) {
- int placeholderStart = template.indexOf("%s", templateStart);
- if (placeholderStart == -1) {
- break;
- }
- builder.append(template.substring(templateStart, placeholderStart));
- builder.append(args[i++]);
- templateStart = placeholderStart + 2;
- }
- builder.append(template.substring(templateStart));
-
- // if we run out of placeholders, append the extra args in square braces
- if (i < args.length) {
- builder.append(" [");
- builder.append(args[i++]);
- while (i < args.length) {
- builder.append(", ");
- builder.append(args[i++]);
- }
- builder.append("]");
- }
-
- return builder.toString();
- }
-}
diff --git a/src/com/android/mail/lib/base/Predicate.java b/src/com/android/mail/lib/base/Predicate.java
deleted file mode 100644
index 009f028..0000000
--- a/src/com/android/mail/lib/base/Predicate.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2007 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.mail.lib.base;
-
-
-/**
- * Determines a true or false value for a given input. For example, a
- * {@code RegexPredicate} might implement {@code Predicate<String>}, and return
- * {@code true} for any string that matches its given regular expression.
- *
- * <p>Implementations which may cause side effects upon evaluation are strongly
- * encouraged to state this fact clearly in their API documentation.
- *
- * @author Kevin Bourrillion
- * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library)
- */
-public interface Predicate<T> {
-
- /*
- * This interface does not extend Function<T, Boolean> because doing so would
- * let predicates return null.
- */
-
- /**
- * Applies this predicate to the given object.
- *
- * @param input the input that the predicate should act on
- * @return the value of this predicate when applied to the input {@code t}
- */
- boolean apply(T input);
-
- /**
- * Indicates whether some other object is equal to this {@code Predicate}.
- * This method can return {@code true} <i>only</i> if the specified object is
- * also a {@code Predicate} and, for every input object {@code input}, it
- * returns exactly the same value. Thus, {@code predicate1.equals(predicate2)}
- * implies that either {@code predicate1.apply(input)} and
- * {@code predicate2.apply(input)} are both {@code true} or both
- * {@code false}.
- *
- * <p>Note that it is always safe <i>not</i> to override
- * {@link Object#equals}.
- */
- boolean equals(Object obj);
-}
diff --git a/src/com/android/mail/lib/base/Splitter.java b/src/com/android/mail/lib/base/Splitter.java
deleted file mode 100644
index 205cd2c..0000000
--- a/src/com/android/mail/lib/base/Splitter.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright (C) 2009 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.mail.lib.base;
-
-import static com.android.mail.lib.base.Preconditions.checkArgument;
-import static com.android.mail.lib.base.Preconditions.checkNotNull;
-import static com.android.mail.lib.base.Preconditions.checkState;
-
-import com.google.common.base.Joiner;
-
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.StringTokenizer;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * An object that divides strings (or other instances of {@code CharSequence})
- * into substrings, by recognizing a <i>separator</i> (a.k.a. "delimiter")
- * which can be expressed as a single character, literal string, regular
- * expression, {@code CharMatcher}, or by using a fixed substring length. This
- * class provides the complementary functionality to {@link Joiner}.
- *
- * <p>Here is the most basic example of {@code Splitter} usage: <pre> {@code
- *
- * Splitter.on(',').split("foo,bar")}</pre>
- *
- * This invocation returns an {@code Iterable<String>} containing {@code "foo"}
- * and {@code "bar"}, in that order.
- *
- * <p>By default {@code Splitter}'s behavior is very simplistic: <pre> {@code
- *
- * Splitter.on(',').split("foo,,bar, quux")}</pre>
- *
- * This returns an iterable containing {@code ["foo", "", "bar", " quux"]}.
- * Notice that the splitter does not assume that you want empty strings removed,
- * or that you wish to trim whitespace. If you want features like these, simply
- * ask for them: <pre> {@code
- *
- * private static final Splitter MY_SPLITTER = Splitter.on(',')
- * .trimResults()
- * .omitEmptyStrings();}</pre>
- *
- * Now {@code MY_SPLITTER.split("foo, ,bar, quux,")} returns an iterable
- * containing just {@code ["foo", "bar", "quux"]}. Note that the order in which
- * the configuration methods are called is never significant; for instance,
- * trimming is always applied first before checking for an empty result,
- * regardless of the order in which the {@link #trimResults()} and
- * {@link #omitEmptyStrings()} methods were invoked.
- *
- * <p><b>Warning: splitter instances are always immutable</b>; a configuration
- * method such as {@code omitEmptyStrings} has no effect on the instance it
- * is invoked on! You must store and use the new splitter instance returned by
- * the method. This makes splitters thread-safe, and safe to store as {@code
- * static final} constants (as illustrated above). <pre> {@code
- *
- * // Bad! Do not do this!
- * Splitter splitter = Splitter.on('/');
- * splitter.trimResults(); // does nothing!
- * return splitter.split("wrong / wrong / wrong");}</pre>
- *
- * The separator recognized by the splitter does not have to be a single
- * literal character as in the examples above. See the methods {@link
- * #on(String)}, {@link #on(Pattern)} and {@link #on(CharMatcher)} for examples
- * of other ways to specify separators.
- *
- * <p><b>Note:</b> this class does not mimic any of the quirky behaviors of
- * similar JDK methods; for instance, it does not silently discard trailing
- * separators, as does {@link String#split(String)}, nor does it have a default
- * behavior of using five particular whitespace characters as separators, like
- * {@link StringTokenizer}.
- *
- * @author Julien Silland
- * @author Jesse Wilson
- * @author Kevin Bourrillion
- * @since 2009.09.15 <b>tentative</b>
- */
-public final class Splitter {
- private final CharMatcher trimmer;
- private final boolean omitEmptyStrings;
- private final Strategy strategy;
-
- private Splitter(Strategy strategy) {
- this(strategy, false, CharMatcher.NONE);
- }
-
- private Splitter(Strategy strategy, boolean omitEmptyStrings,
- CharMatcher trimmer) {
- this.strategy = strategy;
- this.omitEmptyStrings = omitEmptyStrings;
- this.trimmer = trimmer;
- }
-
- /**
- * Returns a splitter that uses the given single-character separator. For
- * example, {@code Splitter.on(',').split("foo,,bar")} returns an iterable
- * containing {@code ["foo", "", "bar"]}.
- *
- * @param separator the character to recognize as a separator
- * @return a splitter, with default settings, that recognizes that separator
- */
- public static Splitter on(char separator) {
- return on(CharMatcher.is(separator));
- }
-
- /**
- * Returns a splitter that considers any single character matched by the
- * given {@code CharMatcher} to be a separator. For example, {@code
- * Splitter.on(CharMatcher.anyOf(";,")).split("foo,;bar,quux")} returns an
- * iterable containing {@code ["foo", "", "bar", "quux"]}.
- *
- * @param separatorMatcher a {@link CharMatcher} that determines whether a
- * character is a separator
- * @return a splitter, with default settings, that uses this matcher
- */
- public static Splitter on(final CharMatcher separatorMatcher) {
- checkNotNull(separatorMatcher);
-
- return new Splitter(new Strategy() {
- /*@Override*/ public SplittingIterator iterator(
- Splitter splitter, final CharSequence toSplit) {
- return new SplittingIterator(splitter, toSplit) {
- @Override int separatorStart(int start) {
- return separatorMatcher.indexIn(toSplit, start);
- }
-
- @Override int separatorEnd(int separatorPosition) {
- return separatorPosition + 1;
- }
- };
- }
- });
- }
-
- /**
- * Returns a splitter that uses the given fixed string as a separator. For
- * example, {@code Splitter.on(", ").split("foo, bar, baz,qux")} returns an
- * iterable containing {@code ["foo", "bar", "baz,qux"]}.
- *
- * @param separator the literal, nonempty string to recognize as a separator
- * @return a splitter, with default settings, that recognizes that separator
- */
- public static Splitter on(final String separator) {
- checkArgument(separator.length() != 0,
- "The separator may not be the empty string.");
-
- return new Splitter(new Strategy() {
- /*@Override*/ public SplittingIterator iterator(
- Splitter splitter, CharSequence toSplit) {
- return new SplittingIterator(splitter, toSplit) {
- @Override public int separatorStart(int start) {
- int delimeterLength = separator.length();
-
- positions:
- for (int p = start, last = toSplit.length() - delimeterLength;
- p <= last; p++) {
- for (int i = 0; i < delimeterLength; i++) {
- if (toSplit.charAt(i + p) != separator.charAt(i)) {
- continue positions;
- }
- }
- return p;
- }
- return -1;
- }
-
- @Override public int separatorEnd(int separatorPosition) {
- return separatorPosition + separator.length();
- }
- };
- }
- });
- }
-
- /**
- * Returns a splitter that considers any subsequence matching {@code
- * pattern} to be a separator. For example, {@code
- * Splitter.on(Pattern.compile("\r?\n")).split(entireFile)} splits a string
- * into lines whether it uses DOS-style or UNIX-style line terminators.
- *
- * @param separatorPattern the pattern that determines whether a subsequence
- * is a separator. This pattern may not match the empty string.
- * @return a splitter, with default settings, that uses this pattern
- * @throws IllegalArgumentException if {@code separatorPattern} matches the
- * empty string
- */
- public static Splitter on(final Pattern separatorPattern) {
- checkNotNull(separatorPattern);
- checkArgument(!separatorPattern.matcher("").matches(),
- "The pattern may not match the empty string: %s", separatorPattern);
-
- return new Splitter(new Strategy() {
- /*@Override*/ public SplittingIterator iterator(
- final Splitter splitter, CharSequence toSplit) {
- final Matcher matcher = separatorPattern.matcher(toSplit);
- return new SplittingIterator(splitter, toSplit) {
- @Override public int separatorStart(int start) {
- return matcher.find(start) ? matcher.start() : -1;
- }
-
- @Override public int separatorEnd(int separatorPosition) {
- return matcher.end();
- }
- };
- }
- });
- }
-
- /**
- * Returns a splitter that considers any subsequence matching a given
- * pattern (regular expression) to be a separator. For example, {@code
- * Splitter.onPattern("\r?\n").split(entireFile)} splits a string into lines
- * whether it uses DOS-style or UNIX-style line terminators. This is
- * equivalent to {@code Splitter.on(Pattern.compile(pattern))}.
- *
- * @param separatorPattern the pattern that determines whether a subsequence
- * is a separator. This pattern may not match the empty string.
- * @return a splitter, with default settings, that uses this pattern
- * @throws PatternSyntaxException if {@code separatorPattern} is a malformed
- * expression
- * @throws IllegalArgumentException if {@code separatorPattern} matches the
- * empty string
- */
- public static Splitter onPattern(String separatorPattern) {
- return on(Pattern.compile(separatorPattern));
- }
-
- /**
- * Returns a splitter that divides strings into pieces of the given length.
- * For example, {@code Splitter.atEach(2).split("abcde")} returns an
- * iterable containing {@code ["ab", "cd", "e"]}. The last piece can be
- * smaller than {@code length} but will never be empty.
- *
- * @param length the desired length of pieces after splitting
- * @return a splitter, with default settings, that can split into fixed sized
- * pieces
- */
- public static Splitter fixedLength(final int length) {
- checkArgument(length > 0, "The length may not be less than 1");
-
- return new Splitter(new Strategy() {
- /*@Override*/ public SplittingIterator iterator(
- final Splitter splitter, CharSequence toSplit) {
- return new SplittingIterator(splitter, toSplit) {
- @Override public int separatorStart(int start) {
- int nextChunkStart = start + length;
- return (nextChunkStart < toSplit.length() ? nextChunkStart : -1);
- }
-
- @Override public int separatorEnd(int separatorPosition) {
- return separatorPosition;
- }
- };
- }
- });
- }
-
- /**
- * Returns a splitter that behaves equivalently to {@code this} splitter, but
- * automatically omits empty strings from the results. For example, {@code
- * Splitter.on(',').omitEmptyStrings().split(",a,,,b,c,,")} returns an
- * iterable containing only {@code ["a", "b", "c"]}.
- *
- * <p>If either {@code trimResults} option is also specified when creating a
- * splitter, that splitter always trims results first before checking for
- * emptiness. So, for example, {@code
- * Splitter.on(':').omitEmptyStrings().trimResults().split(": : : ")} returns
- * an empty iterable.
- *
- * <p>Note that it is ordinarily not possible for {@link #split(CharSequence)}
- * to return an empty iterable, but when using this option, it can (if the
- * input sequence consists of nothing but separators).
- *
- * @return a splitter with the desired configuration
- */
- public Splitter omitEmptyStrings() {
- return new Splitter(strategy, true, trimmer);
- }
-
- /**
- * Returns a splitter that behaves equivalently to {@code this} splitter, but
- * automatically removes leading and trailing {@linkplain
- * CharMatcher#WHITESPACE whitespace} from each returned substring; equivalent
- * to {@code trimResults(CharMatcher.WHITESPACE)}. For example, {@code
- * Splitter.on(',').trimResults().split(" a, b ,c ")} returns an iterable
- * containing {@code ["a", "b", "c"]}.
- *
- * @return a splitter with the desired configuration
- */
- public Splitter trimResults() {
- return trimResults(CharMatcher.WHITESPACE);
- }
-
- /**
- * Returns a splitter that behaves equivalently to {@code this} splitter, but
- * removes all leading or trailing characters matching the given {@code
- * CharMatcher} from each returned substring. For example, {@code
- * Splitter.on(',').trimResults(CharMatcher.is('_')).split("_a ,_b_ ,c__")}
- * returns an iterable containing {@code ["a ", "b_ ", "c"]}.
- *
- * @param trimmer a {@link CharMatcher} that determines whether a character
- * should be removed from the beginning/end of a subsequence
- * @return a splitter with the desired configuration
- */
- public Splitter trimResults(CharMatcher trimmer) {
- checkNotNull(trimmer);
- return new Splitter(strategy, omitEmptyStrings, trimmer);
- }
-
- /**
- * Splits the {@link CharSequence} passed in parameter.
- *
- * @param sequence the sequence of characters to split
- * @return an iteration over the segments split from the parameter.
- */
- public Iterable<String> split(final CharSequence sequence) {
- checkNotNull(sequence);
-
- return new Iterable<String>() {
- /*@Override*/ public Iterator<String> iterator() {
- return strategy.iterator(Splitter.this, sequence);
- }
- };
- }
-
- private interface Strategy {
- Iterator<String> iterator(Splitter splitter, CharSequence toSplit);
- }
-
- private abstract static class SplittingIterator
- extends AbstractIterator<String> {
- final CharSequence toSplit;
- final CharMatcher trimmer;
- final boolean omitEmptyStrings;
-
- /**
- * Returns the first index in {@code toSplit} at or after {@code start}
- * that contains the separator.
- */
- abstract int separatorStart(int start);
-
- /**
- * Returns the first index in {@code toSplit} after {@code
- * separatorPosition} that does not contain a separator. This method is only
- * invoked after a call to {@code separatorStart}.
- */
- abstract int separatorEnd(int separatorPosition);
-
- int offset = 0;
-
- protected SplittingIterator(Splitter splitter, CharSequence toSplit) {
- this.trimmer = splitter.trimmer;
- this.omitEmptyStrings = splitter.omitEmptyStrings;
- this.toSplit = toSplit;
- }
-
- @Override protected String computeNext() {
- while (offset != -1) {
- int start = offset;
- int end;
-
- int separatorPosition = separatorStart(offset);
- if (separatorPosition == -1) {
- end = toSplit.length();
- offset = -1;
- } else {
- end = separatorPosition;
- offset = separatorEnd(separatorPosition);
- }
-
- while (start < end && trimmer.matches(toSplit.charAt(start))) {
- start++;
- }
- while (end > start && trimmer.matches(toSplit.charAt(end - 1))) {
- end--;
- }
-
- if (omitEmptyStrings && start == end) {
- continue;
- }
-
- return toSplit.subSequence(start, end).toString();
- }
- return endOfData();
- }
- }
-
- /*
- * Copied from common.collect.AbstractIterator. TODO: un-fork once these
- * packages have been combined into a single library.
- */
- private static abstract class AbstractIterator<T> implements Iterator<T> {
- State state = State.NOT_READY;
-
- enum State {
- READY, NOT_READY, DONE, FAILED,
- }
-
- T next;
-
- protected abstract T computeNext();
-
- protected final T endOfData() {
- state = State.DONE;
- return null;
- }
-
- public final boolean hasNext() {
- checkState(state != State.FAILED);
- switch (state) {
- case DONE:
- return false;
- case READY:
- return true;
- default:
- }
- return tryToComputeNext();
- }
-
- boolean tryToComputeNext() {
- state = State.FAILED; // temporary pessimism
- next = computeNext();
- if (state != State.DONE) {
- state = State.READY;
- return true;
- }
- return false;
- }
-
- public final T next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- state = State.NOT_READY;
- return next;
- }
-
- /*@Override*/ public void remove() {
- throw new UnsupportedOperationException();
- }
- }
-}
diff --git a/src/com/android/mail/lib/base/StringUtil.java b/src/com/android/mail/lib/base/StringUtil.java
deleted file mode 100644
index 5bfca7f..0000000
--- a/src/com/android/mail/lib/base/StringUtil.java
+++ /dev/null
@@ -1,3205 +0,0 @@
-/*
- * Copyright (C) 2000 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.mail.lib.base;
-
-import static com.android.mail.lib.base.Preconditions.checkArgument;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Joiner.MapJoiner;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Static utility methods and constants pertaining to {@code String} or {@code
- * CharSequence} instances.
- */
-public final class StringUtil {
- private StringUtil() {} // COV_NF_LINE
-
- /**
- * A completely arbitrary selection of eight whitespace characters. See
- * <a href="http://go/white+space">this spreadsheet</a> for more details
- * about whitespace characters.
- *
- * @deprecated Rewrite your code to use {@link CharMatcher#WHITESPACE}, or
- * consider the precise set of characters you want to match and construct
- * the right explicit {@link CharMatcher} or {@link String} for your own
- * purposes.
- */
- @Deprecated
- public static final String WHITE_SPACES = " \r\n\t\u3000\u00A0\u2007\u202F";
-
- /** A string containing the carriage return and linefeed characters. */
- public static final String LINE_BREAKS = "\r\n";
-
- /**
- * Old location of {@link Strings#isNullOrEmpty}; this method will be
- * deprecated soon.
- */
- public static boolean isEmpty(String string) {
- return Strings.isNullOrEmpty(string);
- }
-
- /**
- * Returns {@code true} if the given string is null, empty, or comprises only
- * whitespace characters, as defined by {@link CharMatcher#WHITESPACE}.
- *
- * <p><b>Warning:</b> there are many competing definitions of "whitespace";
- * please see <a href="http://go/white+space">this spreadsheet</a> for
- * details.
- *
- * @param string the string reference to check
- * @return {@code true} if {@code string} is null, empty, or consists of
- * whitespace characters only
- */
- public static boolean isEmptyOrWhitespace(String string) {
- return string == null || CharMatcher.WHITESPACE.matchesAllOf(string);
- }
-
- /**
- * Old location of {@link Strings#nullToEmpty}; this method will be
- * deprecated soon.
- */
- public static String makeSafe(String string) {
- return Strings.nullToEmpty(string);
- }
-
- /**
- * Old location of {@link Strings#emptyToNull}; this method will be
- * deprecated soon.
- */
- public static String toNullIfEmpty(String string) {
- return Strings.emptyToNull(string);
- }
-
- /**
- * Returns the given string if it is nonempty and contains at least one
- * non-whitespace character; {@code null} otherwise. See comment in {@link
- * #isEmptyOrWhitespace} on the definition of whitespace.
- *
- * @param string the string to test and possibly return
- * @return {@code null} if {@code string} is null, empty, or contains only
- * whitespace characters; {@code string} itself otherwise
- */
- public static String toNullIfEmptyOrWhitespace(
- String string) {
- return isEmptyOrWhitespace(string) ? null : string;
- }
-
- /**
- * Old location of {@link Strings#repeat}; this method will be deprecated
- * soon.
- */
- public static String repeat(String string, int count) {
- return Strings.repeat(string, count);
- }
-
- /**
- * Return the first index in the string of any of the specified characters,
- * starting at a given index, or {@code -1} if none of the characters is
- * present.
- *
- * @param string the non-null character sequence to look in
- * @param chars a non-null character sequence containing the set of characters
- * to look for. If empty, this method will find no matches and return
- * {@code -1}
- * @param fromIndex the index of the first character to examine in the input
- * string. If negative, the entire string will be searched. If greater
- * than or equal to the string length, no characters will be searched and
- * {@code -1} will be returned.
- * @return the index of the first match, or {@code -1} if no match was found.
- * Guaranteed to be either {@code -1} or a number greater than or equal to
- * {@code fromIndex}
- * @throws NullPointerException if any argument is null
- */
- // author: pault
- public static int indexOfChars(
- CharSequence string, CharSequence chars, int fromIndex) {
- if (fromIndex >= string.length()) {
- return -1;
- }
-
- /*
- * Prepare lookup structures for the characters. TODO(pault): This loop
- * could be factored into another method to allow caching of the resulting
- * struct if a use-case of very large character sets exists.
- */
- Set<Character> charSet = Collections.emptySet();
- boolean[] charArray = new boolean[128];
- for (int i = 0; i < chars.length(); i++) {
- char c = chars.charAt(i);
- if (c < 128) {
- charArray[c] = true;
- } else {
- if (charSet.isEmpty()) {
- charSet = new HashSet<Character>();
- }
- charSet.add(c);
- }
- }
-
- // Scan the string for matches
- for (int i = Math.max(fromIndex, 0); i < string.length(); i++) {
- char c = string.charAt(i);
- if (c < 128) {
- if (charArray[c]) {
- return i;
- }
- } else if (charSet.contains(c)) {
- return i;
- }
- }
- return -1;
- }
-
-/*
- * -------------------------------------------------------------------
- * This marks the end of the code that has been written or rewritten
- * in 2008 to the quality standards of the Java core libraries group.
- * Code below this point is still awaiting cleanup (you can help!).
- * See http://wiki/Nonconf/JavaCoreLibrariesStandards.
- * -------------------------------------------------------------------
- */
-
-
- /**
- * @param str the string to split. Must not be null.
- * @param delims the delimiter characters. Each character in the
- * string is individually treated as a delimiter.
- * @return an array of tokens. Will not return null. Individual tokens
- * do not have leading/trailing whitespace removed.
- * @deprecated see the detailed instructions under
- * {@link #split(String, String, boolean)}
- */
- @Deprecated
- public static String[] split(String str, String delims) {
- return split(str, delims, false);
- }
-
- /**
- * This method is deprecated because it is too inflexible, providing
- * only a very specific set of behaviors that almost never matches exactly
- * what you intend. Prefer using a {@link Splitter}, which is more flexible
- * and consistent in the way it handles trimming and empty tokens.
- *
- * <ul>
- * <li>Create a {@link Splitter} using {@link Splitter#on(CharMatcher)} such
- * as {@code Splitter.on(CharMatcher.anyOf(delims))}.
- * <li><i>If</i> you need whitespace trimmed from the ends of each segment,
- * adding {@code .trimResults()} to your splitter definition should work
- * in most cases. To match the exact behavior of this method, use
- * {@code .trimResults(CharMatcher.inRange('\0', ' '))}.
- * <li>This method silently ignores empty tokens in the input, but allows
- * empty tokens to appear in the output if {@code trimTokens} is
- * {@code true}. Adding {@code .omitEmptyStrings()} to your splitter
- * definition will filter empty tokens out but will do so <i>after</i>
- * having performed trimming. If you absolutely require this method's
- * behavior in this respect, Splitter is not able to match it.
- * <li>If you need the result as an array, use {@link
- * com.google.common.collect.Iterables#toArray(Iterable, Class)} on the
- * {@code Iterable<String>} returned by {@link Splitter#split}.
- * </ul>
- *
- * @param str the string to split. Must not be null.
- * @param delims the delimiter characters. Each character in the string
- * is individually treated as a delimiter.
- * @param trimTokens if true, leading/trailing whitespace is removed
- * from the tokens
- * @return an array of tokens. Will not return null.
- * @deprecated
- */
- @Deprecated
- public static String[] split(
- String str, String delims, boolean trimTokens) {
- StringTokenizer tokenizer = new StringTokenizer(str, delims);
- int n = tokenizer.countTokens();
- String[] list = new String[n];
- for (int i = 0; i < n; i++) {
- if (trimTokens) {
- list[i] = tokenizer.nextToken().trim();
- } else {
- list[i] = tokenizer.nextToken();
- }
- }
- return list;
- }
-
- /**
- * Trim characters from only the beginning of a string.
- * This is a convenience method, it simply calls trimStart(s, null).
- *
- * @param s String to be trimmed
- * @return String with whitespace characters removed from the beginning
- */
- public static String trimStart(String s) {
- return trimStart(s, null);
- }
-
- /**
- * Trim characters from only the beginning of a string.
- * This method will remove all whitespace characters
- * (defined by Character.isWhitespace(char), in addition to the characters
- * provided, from the end of the provided string.
- *
- * @param s String to be trimmed
- * @param extraChars Characters in addition to whitespace characters that
- * should be trimmed. May be null.
- * @return String with whitespace and characters in extraChars removed
- * from the beginning
- */
- public static String trimStart(String s, String extraChars) {
- int trimCount = 0;
- while (trimCount < s.length()) {
- char ch = s.charAt(trimCount);
- if (Character.isWhitespace(ch)
- || (extraChars != null && extraChars.indexOf(ch) >= 0)) {
- trimCount++;
- } else {
- break;
- }
- }
-
- if (trimCount == 0) {
- return s;
- }
- return s.substring(trimCount);
- }
-
- /**
- * Trim characters from only the end of a string.
- * This is a convenience method, it simply calls trimEnd(s, null).
- *
- * @param s String to be trimmed
- * @return String with whitespace characters removed from the end
- */
- public static String trimEnd(String s) {
- return trimEnd(s, null);
- }
-
- /**
- * Trim characters from only the end of a string.
- * This method will remove all whitespace characters
- * (defined by Character.isWhitespace(char), in addition to the characters
- * provided, from the end of the provided string.
- *
- * @param s String to be trimmed
- * @param extraChars Characters in addition to whitespace characters that
- * should be trimmed. May be null.
- * @return String with whitespace and characters in extraChars removed
- * from the end
- */
- public static String trimEnd(String s, String extraChars) {
- int trimCount = 0;
- while (trimCount < s.length()) {
- char ch = s.charAt(s.length() - trimCount - 1);
- if (Character.isWhitespace(ch)
- || (extraChars != null && extraChars.indexOf(ch) >= 0)) {
- trimCount++;
- } else {
- break;
- }
- }
-
- if (trimCount == 0) {
- return s;
- }
- return s.substring(0, s.length() - trimCount);
- }
-
- /**
- * @param str the string to split. Must not be null.
- * @param delims the delimiter characters. Each character in the
- * string is individually treated as a delimiter.
- * @return an array of tokens. Will not return null. Leading/trailing
- * whitespace is removed from the tokens.
- * @deprecated see the detailed instructions under
- * {@link #split(String, String, boolean)}
- */
- @Deprecated
- public static String[] splitAndTrim(String str, String delims) {
- return split(str, delims, true);
- }
-
- /** Parse comma-separated list of ints and return as array. */
- public static int[] splitInts(String str) throws IllegalArgumentException {
- StringTokenizer tokenizer = new StringTokenizer(str, ",");
- int n = tokenizer.countTokens();
- int[] list = new int[n];
- for (int i = 0; i < n; i++) {
- String token = tokenizer.nextToken();
- list[i] = Integer.parseInt(token);
- }
- return list;
- }
-
- /** Parse comma-separated list of longs and return as array. */
- public static long[] splitLongs(String str) throws IllegalArgumentException {
- StringTokenizer tokenizer = new StringTokenizer(str, ",");
- int n = tokenizer.countTokens();
- long[] list = new long[n];
- for (int i = 0; i < n; i++) {
- String token = tokenizer.nextToken();
- list[i] = Long.parseLong(token);
- }
- return list;
- }
-
- /** This replaces the occurrences of 'what' in 'str' with 'with'
- *
- * @param str the string to process
- * @param what to replace
- * @param with replace with this
- * @return String str where 'what' was replaced with 'with'
- *
- * @deprecated Please use {@link String#replace(CharSequence, CharSequence)}.
- */
- @Deprecated
- public static String replace(
- String str, CharSequence what, CharSequence with) {
- // Have to check this argument, for compatibility with the old impl.
- // For the record, String.replace() is capable of handling an empty target
- // string... but it does something kind of weird in that case.
- checkArgument(what.length() > 0);
- return str.replace(what, with);
- }
-
- private static final Splitter NEWLINE_SPLITTER =
- Splitter.on('\n').omitEmptyStrings();
-
- /**
- * Reformats the given string to a fixed width by inserting carriage returns
- * and trimming unnecessary whitespace. See
- * {@link #fixedWidth(String[], int)} for details. The {@code str} argument
- * to this method will be split on newline characters ({@code '\n'}) only
- * (regardless of platform). An array of resulting non-empty strings is
- * then passed to {@link #fixedWidth(String[], int)} as the {@code lines}
- * parameter.
- *
- * @param str the string to format
- * @param width the fixed width (in characters)
- */
- public static String fixedWidth(String str, int width) {
- List<String> lines = new ArrayList<String>();
-
- for (String line : NEWLINE_SPLITTER.split(str)) {
- lines.add(line);
- }
-
- String[] lineArray = lines.toArray(new String[0]);
- return fixedWidth(lineArray, width);
- }
-
- /**
- * Reformats the given array of lines to a fixed width by inserting
- * newlines and trimming unnecessary whitespace. This uses simple
- * whitespace-based splitting, not sophisticated internationalized
- * line breaking. Newlines within a line are treated like any other
- * whitespace. Lines which are already short enough will be passed
- * through unmodified.
- *
- * <p>Only breaking whitespace characters (those which match
- * {@link CharMatcher#BREAKING_WHITESPACE}) are treated as whitespace by
- * this method. Non-breaking whitespace characters will be considered as
- * ordinary characters which are connected to any other adjacent
- * non-whitespace characters, and will therefore appear in the returned
- * string in their original context.
- *
- * @param lines array of lines to format
- * @param width the fixed width (in characters)
- */
- public static String fixedWidth(String[] lines, int width) {
- List<String> formattedLines = new ArrayList<String>();
-
- for (String line : lines) {
- formattedLines.add(formatLineToFixedWidth(line, width));
- }
-
- return Joiner.on('\n').join(formattedLines);
- }
-
- private static final Splitter TO_WORDS =
- Splitter.on(CharMatcher.BREAKING_WHITESPACE).omitEmptyStrings();
-
- /**
- * Helper method for {@link #fixedWidth(String[], int)}
- */
- private static String formatLineToFixedWidth(String line, int width) {
- if (line.length() <= width) {
- return line;
- }
-
- StringBuilder builder = new StringBuilder();
- int col = 0;
-
- for (String word : TO_WORDS.split(line)) {
- if (col == 0) {
- col = word.length();
- } else {
- int newCol = col + word.length() + 1; // +1 for the space
-
- if (newCol <= width) {
- builder.append(' ');
- col = newCol;
- } else {
- builder.append('\n');
- col = word.length();
- }
- }
-
- builder.append(word);
- }
-
- return builder.toString();
- }
-
- /**
- * Splits the argument original into a list of substrings. All the
- * substrings in the returned list (except possibly the last) will
- * have length lineLen.
- *
- * @param lineLen the length of the substrings to put in the list
- * @param original the original string
- *
- * @return a list of strings of length lineLen that together make up the
- * original string
- * @deprecated use {@code Splitter.fixedLength(lineLen).split(original))}
- * (note that it returns an {@code Iterable}, not a {@code List})
- */
- @Deprecated
- public static List<String> fixedSplit(String original, int lineLen) {
- List<String> output = new ArrayList<String>();
- for (String elem : Splitter.fixedLength(lineLen).split(original)) {
- output.add(elem);
- }
- return output;
- }
-
- /**
- * Indents the given String per line.
- * @param iString the string to indent
- * @param iIndentDepth the depth of the indentation
- * @return the indented string
- */
- public static String indent(String iString, int iIndentDepth) {
- StringBuilder spacer = new StringBuilder();
- spacer.append("\n");
- for (int i = 0; i < iIndentDepth; i++) {
- spacer.append(" ");
- }
- return iString.replace("\n", spacer.toString());
- }
-
- /**
- * This is a both way strip.
- *
- * @param str the string to strip
- * @param left strip from left
- * @param right strip from right
- * @param what character(s) to strip
- * @return the stripped string
- * @deprecated ensure the string is not null and use
- * <ul>
- * <li> {@code CharMatcher.anyOf(what).trimFrom(str)}
- * if {@code left == true} and {@code right == true}
- * <li> {@code CharMatcher.anyOf(what).trimLeadingFrom(str)}
- * if {@code left == true} and {@code right == false}
- * <li> {@code CharMatcher.anyOf(what).trimTrailingFrom(str)}
- * if {@code left == false} and {@code right == true}
- * </ul>
- */
- @Deprecated
- public static String megastrip(String str,
- boolean left, boolean right,
- String what) {
- if (str == null) {
- return null;
- }
-
- CharMatcher matcher = CharMatcher.anyOf(what);
- if (left) {
- if (right) {
- return matcher.trimFrom(str);
- }
- return matcher.trimLeadingFrom(str);
- }
- if (right) {
- return matcher.trimTrailingFrom(str);
- }
- return str;
- }
-
- /** strip - strips both ways
- *
- * @param str what to strip
- * @return String the striped string
- * @deprecated ensure the string is not null and use {@code
- * CharMatcher.LEGACY_WHITESPACE.trimFrom(str)}; also consider whether you
- * really want the legacy whitespace definition, or something more
- * standard like {@link CharMatcher#WHITESPACE}.
- */
- @SuppressWarnings("deprecation") // this is deprecated itself
- @Deprecated public static String strip(String str) {
- return (str == null) ? null : CharMatcher.LEGACY_WHITESPACE.trimFrom(str);
- }
-
- /** Strip white spaces from both end, and collapse white spaces
- * in the middle.
- *
- * @param str what to strip
- * @return String the striped and collapsed string
- * @deprecated ensure the string is not null and use {@code
- * CharMatcher.LEGACY_WHITESPACE.trimAndCollapseFrom(str, ' ')}; also
- * consider whether you really want the legacy whitespace definition, or
- * something more standard like {@link CharMatcher#WHITESPACE}.
- */
- @SuppressWarnings("deprecation") // this is deprecated itself
- @Deprecated public static String stripAndCollapse(String str) {
- return (str == null) ? null
- : CharMatcher.LEGACY_WHITESPACE.trimAndCollapseFrom(str, ' ');
- }
-
- /**
- * Give me a string and a potential prefix, and I return the string
- * following the prefix if the prefix matches, else null.
- * Analogous to the c++ functions strprefix and var_strprefix.
- *
- * @param str the string to strip
- * @param prefix the expected prefix
- * @return the stripped string or <code>null</code> if the string
- * does not start with the prefix
- */
- public static String stripPrefix(String str, String prefix) {
- return str.startsWith(prefix)
- ? str.substring(prefix.length())
- : null;
- }
-
- /**
- * Case insensitive version of stripPrefix. Strings are compared in
- * the same way as in {@link String#equalsIgnoreCase}.
- * Analogous to the c++ functions strcaseprefix and var_strcaseprefix.
- *
- * @param str the string to strip
- * @param prefix the expected prefix
- * @return the stripped string or <code>null</code> if the string
- * does not start with the prefix
- */
- public static String stripPrefixIgnoreCase(String str, String prefix) {
- return startsWithIgnoreCase(str, prefix)
- ? str.substring(prefix.length())
- : null;
- }
-
- /**
- * Give me a string and a potential suffix, and I return the string
- * before the suffix if the suffix matches, else null.
- * Analogous to the c++ function strsuffix.
- *
- * @param str the string to strip
- * @param suffix the expected suffix
- * @return the stripped string or <code>null</code> if the string
- * does not end with the suffix
- */
- public static String stripSuffix(String str, String suffix) {
- return str.endsWith(suffix)
- ? str.substring(0, str.length() - suffix.length())
- : null;
- }
-
- /**
- * Case insensitive version of stripSuffix. Strings are compared in
- * the same way as in {@link String#equalsIgnoreCase}.
- * Analogous to the c++ function strcasesuffix.
- *
- * @param str the string to strip
- * @param suffix the expected suffix
- * @return the stripped string or <code>null</code> if the string
- * does not end with the suffix
- */
- public static String stripSuffixIgnoreCase(
- String str, String suffix) {
- return endsWithIgnoreCase(str, suffix)
- ? str.substring(0, str.length() - suffix.length())
- : null;
- }
-
- /**
- * Strips all non-digit characters from a string.
- *
- * The resulting string will only contain characters for which isDigit()
- * returns true.
- *
- * @param str the string to strip
- * @return a string consisting of digits only, or an empty string
- * @deprecated use {@code CharMatcher.JAVA_DIGIT.retainFrom(str)} (also
- * consider whether this is really the definition of "digit" you wish to
- * use)
- */
- @Deprecated public static String stripNonDigits(String str) {
- return CharMatcher.JAVA_DIGIT.retainFrom(str);
- }
-
- /**
- * Finds the last index in str of a character not in the characters
- * in 'chars' (similar to ANSI string.find_last_not_of).
- *
- * Returns -1 if no such character can be found.
- *
- * <p><b>Note:</b> If {@code fromIndex} is zero, use {@link CharMatcher}
- * instead for this: {@code CharMatcher.noneOf(chars).lastIndexIn(str)}.
- */
- // TODO(kevinb): after adding fromIndex versions of (last)IndexOf to
- // CharMatcher, deprecate this
- public static int lastIndexNotOf(String str, String chars, int fromIndex) {
- fromIndex = Math.min(fromIndex, str.length() - 1);
-
- for (int pos = fromIndex; pos >= 0; pos--) {
- if (chars.indexOf(str.charAt(pos)) < 0) {
- return pos;
- }
- }
-
- return -1;
- }
-
- /**
- * Like String.replace() except that it accepts any number of old chars.
- * Replaces any occurrances of 'oldchars' in 'str' with 'newchar'.
- * Example: replaceChars("Hello, world!", "H,!", ' ') returns " ello world "
- *
- * @deprecated use {@code CharMatcher#replaceFrom(String, char)}, for example
- * {@code CharMatcher.anyOf(oldchars).replaceFrom(str, newchar)}
- */
- @Deprecated public static String replaceChars(
- String str, CharSequence oldchars, char newchar) {
- return CharMatcher.anyOf(oldchars).replaceFrom(str, newchar);
- }
-
- /**
- * Remove any occurrances of 'oldchars' in 'str'.
- * Example: removeChars("Hello, world!", ",!") returns "Hello world"
- *
- * @deprecated use {@link CharMatcher#removeFrom(CharSequence)}, for example
- * {@code CharMatcher.anyOf(oldchars).removeFrom(str)}
- */
- @Deprecated public static String removeChars(
- String str, CharSequence oldchars) {
- return CharMatcher.anyOf(oldchars).removeFrom(str);
- }
-
- // See http://www.microsoft.com/typography/unicode/1252.htm
- private static final CharMatcher FANCY_SINGLE_QUOTE
- = CharMatcher.anyOf("\u0091\u0092\u2018\u2019");
- private static final CharMatcher FANCY_DOUBLE_QUOTE
- = CharMatcher.anyOf("\u0093\u0094\u201c\u201d");
-
- /**
- * Replaces microsoft "smart quotes" (curly " and ') with their
- * ascii counterparts.
- */
- public static String replaceSmartQuotes(String str) {
- String tmp = FANCY_SINGLE_QUOTE.replaceFrom(str, '\'');
- return FANCY_DOUBLE_QUOTE.replaceFrom(tmp, '"');
- }
-
- /**
- * Convert a string of hex digits to a byte array, with the first
- * byte in the array being the MSB. The string passed in should be
- * just the raw digits (upper or lower case), with no leading
- * or trailing characters (like '0x' or 'h').
- * An odd number of characters is supported.
- * If the string is empty, an empty array will be returned.
- *
- * This is significantly faster than using
- * new BigInteger(str, 16).toByteArray();
- * especially with larger strings. Here are the results of some
- * microbenchmarks done on a P4 2.8GHz 2GB RAM running
- * linux 2.4.22-gg11 and JDK 1.5 with an optimized build:
- *
- * String length hexToBytes (usec) BigInteger
- * -----------------------------------------------------
- * 16 0.570 1.43
- * 256 8.21 44.4
- * 1024 32.8 526
- * 16384 546 121000
- */
- public static byte[] hexToBytes(CharSequence str) {
- byte[] bytes = new byte[(str.length() + 1) / 2];
- if (str.length() == 0) {
- return bytes;
- }
- bytes[0] = 0;
- int nibbleIdx = (str.length() % 2);
- for (int i = 0; i < str.length(); i++) {
- char c = str.charAt(i);
- if (!isHex(c)) {
- throw new IllegalArgumentException("string contains non-hex chars");
- }
- if ((nibbleIdx % 2) == 0) {
- bytes[nibbleIdx >> 1] = (byte) (hexValue(c) << 4);
- } else {
- bytes[nibbleIdx >> 1] += (byte) hexValue(c);
- }
- nibbleIdx++;
- }
- return bytes;
- }
-
- /**
- * Converts any instances of "\r" or "\r\n" style EOLs into "\n" (Line Feed).
- */
- public static String convertEOLToLF(String input) {
- StringBuilder res = new StringBuilder(input.length());
- char[] s = input.toCharArray();
- int from = 0;
- final int end = s.length;
- for (int i = 0; i < end; i++) {
- if (s[i] == '\r') {
- res.append(s, from, i - from);
- res.append('\n');
- if (i + 1 < end && s[i + 1] == '\n') {
- i++;
- }
-
- from = i + 1;
- }
- }
-
- if (from == 0) { // no \r!
- return input;
- }
-
- res.append(s, from, end - from);
- return res.toString();
- }
-
- /**
- * Old location of {@link Strings#padStart}; this method will be deprecated
- * soon.
- */
- public static String padLeft(String s, int len, char padChar) {
- return Strings.padStart(s, len, padChar);
- }
-
- /**
- * Old location of {@link Strings#padEnd}; this method will be deprecated
- * soon.
- */
- public static String padRight(String s, int len, char padChar) {
- return Strings.padEnd(s, len, padChar);
- }
-
- /**
- * Returns a string consisting of "s", with each of the first "len" characters
- * replaced by "maskChar" character.
- */
- public static String maskLeft(String s, int len, char maskChar) {
- if (len <= 0) {
- return s;
- }
- len = Math.min(len, s.length());
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < len; i++) {
- sb.append(maskChar);
- }
- sb.append(s.substring(len));
- return sb.toString();
- }
-
- private static boolean isOctal(char c) {
- return (c >= '0') && (c <= '7');
- }
-
- private static boolean isHex(char c) {
- return ((c >= '0') && (c <= '9')) ||
- ((c >= 'a') && (c <= 'f')) ||
- ((c >= 'A') && (c <= 'F'));
- }
-
- private static int hexValue(char c) {
- if ((c >= '0') && (c <= '9')) {
- return (c - '0');
- } else if ((c >= 'a') && (c <= 'f')) {
- return (c - 'a') + 10;
- } else {
- return (c - 'A') + 10;
- }
- }
-
- /**
- * Unescape any C escape sequences (\n, \r, \\, \ooo, etc) and return the
- * resulting string.
- */
- public static String unescapeCString(String s) {
- if (s.indexOf('\\') < 0) {
- // Fast path: nothing to unescape
- return s;
- }
-
- StringBuilder sb = new StringBuilder();
- int len = s.length();
- for (int i = 0; i < len;) {
- char c = s.charAt(i++);
- if (c == '\\' && (i < len)) {
- c = s.charAt(i++);
- switch (c) {
- case 'a': c = '\007'; break;
- case 'b': c = '\b'; break;
- case 'f': c = '\f'; break;
- case 'n': c = '\n'; break;
- case 'r': c = '\r'; break;
- case 't': c = '\t'; break;
- case 'v': c = '\013'; break;
- case '\\': c = '\\'; break;
- case '?': c = '?'; break;
- case '\'': c = '\''; break;
- case '"': c = '\"'; break;
-
- default: {
- if ((c == 'x') && (i < len) && isHex(s.charAt(i))) {
- // "\xXX"
- int v = hexValue(s.charAt(i++));
- if ((i < len) && isHex(s.charAt(i))) {
- v = v * 16 + hexValue(s.charAt(i++));
- }
- c = (char) v;
- } else if (isOctal(c)) {
- // "\OOO"
- int v = (c - '0');
- if ((i < len) && isOctal(s.charAt(i))) {
- v = v * 8 + (s.charAt(i++) - '0');
- }
- if ((i < len) && isOctal(s.charAt(i))) {
- v = v * 8 + (s.charAt(i++) - '0');
- }
- c = (char) v;
- } else {
- // Propagate unknown escape sequences.
- sb.append('\\');
- }
- break;
- }
- }
- }
- sb.append(c);
- }
- return sb.toString();
- }
-
- /**
- * Unescape any MySQL escape sequences.
- * See MySQL language reference Chapter 6 at
- * <a href="http://www.mysql.com/doc/">http://www.mysql.com/doc/</a>.
- * This function will <strong>not</strong> work for other SQL-like
- * dialects.
- * @param s string to unescape, with the surrounding quotes.
- * @return unescaped string, without the surrounding quotes.
- * @exception IllegalArgumentException if s is not a valid MySQL string.
- */
- public static String unescapeMySQLString(String s)
- throws IllegalArgumentException {
- // note: the same buffer is used for both reading and writing
- // it works because the writer can never outrun the reader
- char chars[] = s.toCharArray();
-
- // the string must be quoted 'like this' or "like this"
- if (chars.length < 2 || chars[0] != chars[chars.length - 1] ||
- (chars[0] != '\'' && chars[0] != '"')) {
- throw new IllegalArgumentException("not a valid MySQL string: " + s);
- }
-
- // parse the string and decode the backslash sequences; in addition,
- // quotes can be escaped 'like this: ''', "like this: """, or 'like this: "'
- int j = 1; // write position in the string (never exceeds read position)
- int f = 0; // state: 0 (normal), 1 (backslash), 2 (quote)
- for (int i = 1; i < chars.length - 1; i++) {
- if (f == 0) { // previous character was normal
- if (chars[i] == '\\') {
- f = 1; // backslash
- } else if (chars[i] == chars[0]) {
- f = 2; // quoting character
- } else {
- chars[j++] = chars[i];
- }
- } else if (f == 1) { // previous character was a backslash
- switch (chars[i]) {
- case '0': chars[j++] = '\0'; break;
- case '\'': chars[j++] = '\''; break;
- case '"': chars[j++] = '"'; break;
- case 'b': chars[j++] = '\b'; break;
- case 'n': chars[j++] = '\n'; break;
- case 'r': chars[j++] = '\r'; break;
- case 't': chars[j++] = '\t'; break;
- case 'z': chars[j++] = '\032'; break;
- case '\\': chars[j++] = '\\'; break;
- default:
- // if the character is not special, backslash disappears
- chars[j++] = chars[i];
- break;
- }
- f = 0;
- } else { // previous character was a quote
- // quoting characters must be doubled inside a string
- if (chars[i] != chars[0]) {
- throw new IllegalArgumentException("not a valid MySQL string: " + s);
- }
- chars[j++] = chars[0];
- f = 0;
- }
- }
- // string contents cannot end with a special character
- if (f != 0) {
- throw new IllegalArgumentException("not a valid MySQL string: " + s);
- }
-
- // done
- return new String(chars, 1, j - 1);
- }
-
- // TODO(pbarry): move all HTML methods to common.html package
-
- static final Map<String, Character> ESCAPE_STRINGS;
- static final Set<Character> HEX_LETTERS;
-
- static {
- // HTML character entity references as defined in HTML 4
- // see http://www.w3.org/TR/REC-html40/sgml/entities.html
- ESCAPE_STRINGS = new HashMap<String, Character>(252);
-
- ESCAPE_STRINGS.put(" ", '\u00A0');
- ESCAPE_STRINGS.put("¡", '\u00A1');
- ESCAPE_STRINGS.put("¢", '\u00A2');
- ESCAPE_STRINGS.put("£", '\u00A3');
- ESCAPE_STRINGS.put("¤", '\u00A4');
- ESCAPE_STRINGS.put("¥", '\u00A5');
- ESCAPE_STRINGS.put("¦", '\u00A6');
- ESCAPE_STRINGS.put("§", '\u00A7');
- ESCAPE_STRINGS.put("¨", '\u00A8');
- ESCAPE_STRINGS.put("©", '\u00A9');
- ESCAPE_STRINGS.put("ª", '\u00AA');
- ESCAPE_STRINGS.put("«", '\u00AB');
- ESCAPE_STRINGS.put("¬", '\u00AC');
- ESCAPE_STRINGS.put("­", '\u00AD');
- ESCAPE_STRINGS.put("®", '\u00AE');
- ESCAPE_STRINGS.put("¯", '\u00AF');
- ESCAPE_STRINGS.put("°", '\u00B0');
- ESCAPE_STRINGS.put("±", '\u00B1');
- ESCAPE_STRINGS.put("²", '\u00B2');
- ESCAPE_STRINGS.put("³", '\u00B3');
- ESCAPE_STRINGS.put("´", '\u00B4');
- ESCAPE_STRINGS.put("µ", '\u00B5');
- ESCAPE_STRINGS.put("¶", '\u00B6');
- ESCAPE_STRINGS.put("·", '\u00B7');
- ESCAPE_STRINGS.put("¸", '\u00B8');
- ESCAPE_STRINGS.put("¹", '\u00B9');
- ESCAPE_STRINGS.put("º", '\u00BA');
- ESCAPE_STRINGS.put("»", '\u00BB');
- ESCAPE_STRINGS.put("¼", '\u00BC');
- ESCAPE_STRINGS.put("½", '\u00BD');
- ESCAPE_STRINGS.put("¾", '\u00BE');
- ESCAPE_STRINGS.put("¿", '\u00BF');
- ESCAPE_STRINGS.put("À", '\u00C0');
- ESCAPE_STRINGS.put("Á", '\u00C1');
- ESCAPE_STRINGS.put("Â", '\u00C2');
- ESCAPE_STRINGS.put("Ã", '\u00C3');
- ESCAPE_STRINGS.put("Ä", '\u00C4');
- ESCAPE_STRINGS.put("Å", '\u00C5');
- ESCAPE_STRINGS.put("Æ", '\u00C6');
- ESCAPE_STRINGS.put("Ç", '\u00C7');
- ESCAPE_STRINGS.put("È", '\u00C8');
- ESCAPE_STRINGS.put("É", '\u00C9');
- ESCAPE_STRINGS.put("Ê", '\u00CA');
- ESCAPE_STRINGS.put("Ë", '\u00CB');
- ESCAPE_STRINGS.put("Ì", '\u00CC');
- ESCAPE_STRINGS.put("Í", '\u00CD');
- ESCAPE_STRINGS.put("Î", '\u00CE');
- ESCAPE_STRINGS.put("Ï", '\u00CF');
- ESCAPE_STRINGS.put("Ð", '\u00D0');
- ESCAPE_STRINGS.put("Ñ", '\u00D1');
- ESCAPE_STRINGS.put("Ò", '\u00D2');
- ESCAPE_STRINGS.put("Ó", '\u00D3');
- ESCAPE_STRINGS.put("Ô", '\u00D4');
- ESCAPE_STRINGS.put("Õ", '\u00D5');
- ESCAPE_STRINGS.put("Ö", '\u00D6');
- ESCAPE_STRINGS.put("×", '\u00D7');
- ESCAPE_STRINGS.put("Ø", '\u00D8');
- ESCAPE_STRINGS.put("Ù", '\u00D9');
- ESCAPE_STRINGS.put("Ú", '\u00DA');
- ESCAPE_STRINGS.put("Û", '\u00DB');
- ESCAPE_STRINGS.put("Ü", '\u00DC');
- ESCAPE_STRINGS.put("Ý", '\u00DD');
- ESCAPE_STRINGS.put("Þ", '\u00DE');
- ESCAPE_STRINGS.put("ß", '\u00DF');
- ESCAPE_STRINGS.put("à", '\u00E0');
- ESCAPE_STRINGS.put("á", '\u00E1');
- ESCAPE_STRINGS.put("â", '\u00E2');
- ESCAPE_STRINGS.put("ã", '\u00E3');
- ESCAPE_STRINGS.put("ä", '\u00E4');
- ESCAPE_STRINGS.put("å", '\u00E5');
- ESCAPE_STRINGS.put("æ", '\u00E6');
- ESCAPE_STRINGS.put("ç", '\u00E7');
- ESCAPE_STRINGS.put("è", '\u00E8');
- ESCAPE_STRINGS.put("é", '\u00E9');
- ESCAPE_STRINGS.put("ê", '\u00EA');
- ESCAPE_STRINGS.put("ë", '\u00EB');
- ESCAPE_STRINGS.put("ì", '\u00EC');
- ESCAPE_STRINGS.put("í", '\u00ED');
- ESCAPE_STRINGS.put("î", '\u00EE');
- ESCAPE_STRINGS.put("ï", '\u00EF');
- ESCAPE_STRINGS.put("ð", '\u00F0');
- ESCAPE_STRINGS.put("ñ", '\u00F1');
- ESCAPE_STRINGS.put("ò", '\u00F2');
- ESCAPE_STRINGS.put("ó", '\u00F3');
- ESCAPE_STRINGS.put("ô", '\u00F4');
- ESCAPE_STRINGS.put("õ", '\u00F5');
- ESCAPE_STRINGS.put("ö", '\u00F6');
- ESCAPE_STRINGS.put("÷", '\u00F7');
- ESCAPE_STRINGS.put("ø", '\u00F8');
- ESCAPE_STRINGS.put("ù", '\u00F9');
- ESCAPE_STRINGS.put("ú", '\u00FA');
- ESCAPE_STRINGS.put("û", '\u00FB');
- ESCAPE_STRINGS.put("ü", '\u00FC');
- ESCAPE_STRINGS.put("ý", '\u00FD');
- ESCAPE_STRINGS.put("þ", '\u00FE');
- ESCAPE_STRINGS.put("ÿ", '\u00FF');
- ESCAPE_STRINGS.put("&fnof", '\u0192');
- ESCAPE_STRINGS.put("&Alpha", '\u0391');
- ESCAPE_STRINGS.put("&Beta", '\u0392');
- ESCAPE_STRINGS.put("&Gamma", '\u0393');
- ESCAPE_STRINGS.put("&Delta", '\u0394');
- ESCAPE_STRINGS.put("&Epsilon", '\u0395');
- ESCAPE_STRINGS.put("&Zeta", '\u0396');
- ESCAPE_STRINGS.put("&Eta", '\u0397');
- ESCAPE_STRINGS.put("&Theta", '\u0398');
- ESCAPE_STRINGS.put("&Iota", '\u0399');
- ESCAPE_STRINGS.put("&Kappa", '\u039A');
- ESCAPE_STRINGS.put("&Lambda", '\u039B');
- ESCAPE_STRINGS.put("&Mu", '\u039C');
- ESCAPE_STRINGS.put("&Nu", '\u039D');
- ESCAPE_STRINGS.put("&Xi", '\u039E');
- ESCAPE_STRINGS.put("&Omicron", '\u039F');
- ESCAPE_STRINGS.put("&Pi", '\u03A0');
- ESCAPE_STRINGS.put("&Rho", '\u03A1');
- ESCAPE_STRINGS.put("&Sigma", '\u03A3');
- ESCAPE_STRINGS.put("&Tau", '\u03A4');
- ESCAPE_STRINGS.put("&Upsilon", '\u03A5');
- ESCAPE_STRINGS.put("&Phi", '\u03A6');
- ESCAPE_STRINGS.put("&Chi", '\u03A7');
- ESCAPE_STRINGS.put("&Psi", '\u03A8');
- ESCAPE_STRINGS.put("&Omega", '\u03A9');
- ESCAPE_STRINGS.put("&alpha", '\u03B1');
- ESCAPE_STRINGS.put("&beta", '\u03B2');
- ESCAPE_STRINGS.put("&gamma", '\u03B3');
- ESCAPE_STRINGS.put("&delta", '\u03B4');
- ESCAPE_STRINGS.put("&epsilon", '\u03B5');
- ESCAPE_STRINGS.put("&zeta", '\u03B6');
- ESCAPE_STRINGS.put("&eta", '\u03B7');
- ESCAPE_STRINGS.put("&theta", '\u03B8');
- ESCAPE_STRINGS.put("&iota", '\u03B9');
- ESCAPE_STRINGS.put("&kappa", '\u03BA');
- ESCAPE_STRINGS.put("&lambda", '\u03BB');
- ESCAPE_STRINGS.put("&mu", '\u03BC');
- ESCAPE_STRINGS.put("&nu", '\u03BD');
- ESCAPE_STRINGS.put("&xi", '\u03BE');
- ESCAPE_STRINGS.put("&omicron", '\u03BF');
- ESCAPE_STRINGS.put("&pi", '\u03C0');
- ESCAPE_STRINGS.put("&rho", '\u03C1');
- ESCAPE_STRINGS.put("&sigmaf", '\u03C2');
- ESCAPE_STRINGS.put("&sigma", '\u03C3');
- ESCAPE_STRINGS.put("&tau", '\u03C4');
- ESCAPE_STRINGS.put("&upsilon", '\u03C5');
- ESCAPE_STRINGS.put("&phi", '\u03C6');
- ESCAPE_STRINGS.put("&chi", '\u03C7');
- ESCAPE_STRINGS.put("&psi", '\u03C8');
- ESCAPE_STRINGS.put("&omega", '\u03C9');
- ESCAPE_STRINGS.put("&thetasym", '\u03D1');
- ESCAPE_STRINGS.put("&upsih", '\u03D2');
- ESCAPE_STRINGS.put("&piv", '\u03D6');
- ESCAPE_STRINGS.put("&bull", '\u2022');
- ESCAPE_STRINGS.put("&hellip", '\u2026');
- ESCAPE_STRINGS.put("&prime", '\u2032');
- ESCAPE_STRINGS.put("&Prime", '\u2033');
- ESCAPE_STRINGS.put("&oline", '\u203E');
- ESCAPE_STRINGS.put("&frasl", '\u2044');
- ESCAPE_STRINGS.put("&weierp", '\u2118');
- ESCAPE_STRINGS.put("&image", '\u2111');
- ESCAPE_STRINGS.put("&real", '\u211C');
- ESCAPE_STRINGS.put("&trade", '\u2122');
- ESCAPE_STRINGS.put("&alefsym", '\u2135');
- ESCAPE_STRINGS.put("&larr", '\u2190');
- ESCAPE_STRINGS.put("&uarr", '\u2191');
- ESCAPE_STRINGS.put("&rarr", '\u2192');
- ESCAPE_STRINGS.put("&darr", '\u2193');
- ESCAPE_STRINGS.put("&harr", '\u2194');
- ESCAPE_STRINGS.put("&crarr", '\u21B5');
- ESCAPE_STRINGS.put("&lArr", '\u21D0');
- ESCAPE_STRINGS.put("&uArr", '\u21D1');
- ESCAPE_STRINGS.put("&rArr", '\u21D2');
- ESCAPE_STRINGS.put("&dArr", '\u21D3');
- ESCAPE_STRINGS.put("&hArr", '\u21D4');
- ESCAPE_STRINGS.put("&forall", '\u2200');
- ESCAPE_STRINGS.put("&part", '\u2202');
- ESCAPE_STRINGS.put("&exist", '\u2203');
- ESCAPE_STRINGS.put("&empty", '\u2205');
- ESCAPE_STRINGS.put("&nabla", '\u2207');
- ESCAPE_STRINGS.put("&isin", '\u2208');
- ESCAPE_STRINGS.put("¬in", '\u2209');
- ESCAPE_STRINGS.put("&ni", '\u220B');
- ESCAPE_STRINGS.put("&prod", '\u220F');
- ESCAPE_STRINGS.put("&sum", '\u2211');
- ESCAPE_STRINGS.put("&minus", '\u2212');
- ESCAPE_STRINGS.put("&lowast", '\u2217');
- ESCAPE_STRINGS.put("&radic", '\u221A');
- ESCAPE_STRINGS.put("&prop", '\u221D');
- ESCAPE_STRINGS.put("&infin", '\u221E');
- ESCAPE_STRINGS.put("&ang", '\u2220');
- ESCAPE_STRINGS.put("&and", '\u2227');
- ESCAPE_STRINGS.put("&or", '\u2228');
- ESCAPE_STRINGS.put("&cap", '\u2229');
- ESCAPE_STRINGS.put("&cup", '\u222A');
- ESCAPE_STRINGS.put("&int", '\u222B');
- ESCAPE_STRINGS.put("&there4", '\u2234');
- ESCAPE_STRINGS.put("&sim", '\u223C');
- ESCAPE_STRINGS.put("&cong", '\u2245');
- ESCAPE_STRINGS.put("&asymp", '\u2248');
- ESCAPE_STRINGS.put("&ne", '\u2260');
- ESCAPE_STRINGS.put("&equiv", '\u2261');
- ESCAPE_STRINGS.put("&le", '\u2264');
- ESCAPE_STRINGS.put("&ge", '\u2265');
- ESCAPE_STRINGS.put("&sub", '\u2282');
- ESCAPE_STRINGS.put("&sup", '\u2283');
- ESCAPE_STRINGS.put("&nsub", '\u2284');
- ESCAPE_STRINGS.put("&sube", '\u2286');
- ESCAPE_STRINGS.put("&supe", '\u2287');
- ESCAPE_STRINGS.put("&oplus", '\u2295');
- ESCAPE_STRINGS.put("&otimes", '\u2297');
- ESCAPE_STRINGS.put("&perp", '\u22A5');
- ESCAPE_STRINGS.put("&sdot", '\u22C5');
- ESCAPE_STRINGS.put("&lceil", '\u2308');
- ESCAPE_STRINGS.put("&rceil", '\u2309');
- ESCAPE_STRINGS.put("&lfloor", '\u230A');
- ESCAPE_STRINGS.put("&rfloor", '\u230B');
- ESCAPE_STRINGS.put("&lang", '\u2329');
- ESCAPE_STRINGS.put("&rang", '\u232A');
- ESCAPE_STRINGS.put("&loz", '\u25CA');
- ESCAPE_STRINGS.put("&spades", '\u2660');
- ESCAPE_STRINGS.put("&clubs", '\u2663');
- ESCAPE_STRINGS.put("&hearts", '\u2665');
- ESCAPE_STRINGS.put("&diams", '\u2666');
- ESCAPE_STRINGS.put(""", '\u0022');
- ESCAPE_STRINGS.put("&", '\u0026');
- ESCAPE_STRINGS.put("<", '\u003C');
- ESCAPE_STRINGS.put(">", '\u003E');
- ESCAPE_STRINGS.put("&OElig", '\u0152');
- ESCAPE_STRINGS.put("&oelig", '\u0153');
- ESCAPE_STRINGS.put("&Scaron", '\u0160');
- ESCAPE_STRINGS.put("&scaron", '\u0161');
- ESCAPE_STRINGS.put("&Yuml", '\u0178');
- ESCAPE_STRINGS.put("&circ", '\u02C6');
- ESCAPE_STRINGS.put("&tilde", '\u02DC');
- ESCAPE_STRINGS.put("&ensp", '\u2002');
- ESCAPE_STRINGS.put("&emsp", '\u2003');
- ESCAPE_STRINGS.put("&thinsp", '\u2009');
- ESCAPE_STRINGS.put("&zwnj", '\u200C');
- ESCAPE_STRINGS.put("&zwj", '\u200D');
- ESCAPE_STRINGS.put("&lrm", '\u200E');
- ESCAPE_STRINGS.put("&rlm", '\u200F');
- ESCAPE_STRINGS.put("&ndash", '\u2013');
- ESCAPE_STRINGS.put("&mdash", '\u2014');
- ESCAPE_STRINGS.put("&lsquo", '\u2018');
- ESCAPE_STRINGS.put("&rsquo", '\u2019');
- ESCAPE_STRINGS.put("&sbquo", '\u201A');
- ESCAPE_STRINGS.put("&ldquo", '\u201C');
- ESCAPE_STRINGS.put("&rdquo", '\u201D');
- ESCAPE_STRINGS.put("&bdquo", '\u201E');
- ESCAPE_STRINGS.put("&dagger", '\u2020');
- ESCAPE_STRINGS.put("&Dagger", '\u2021');
- ESCAPE_STRINGS.put("&permil", '\u2030');
- ESCAPE_STRINGS.put("&lsaquo", '\u2039');
- ESCAPE_STRINGS.put("&rsaquo", '\u203A');
- ESCAPE_STRINGS.put("&euro", '\u20AC');
-
- HEX_LETTERS = new HashSet<Character>(12);
-
- HEX_LETTERS.add('a');
- HEX_LETTERS.add('A');
- HEX_LETTERS.add('b');
- HEX_LETTERS.add('B');
- HEX_LETTERS.add('c');
- HEX_LETTERS.add('C');
- HEX_LETTERS.add('d');
- HEX_LETTERS.add('D');
- HEX_LETTERS.add('e');
- HEX_LETTERS.add('E');
- HEX_LETTERS.add('f');
- HEX_LETTERS.add('F');
- }
-
- /**
- * <p>
- * Replace all the occurences of HTML escape strings with the
- * respective characters.
- * </p>
- * <p>
- * The default mode is strict (requiring semicolons).
- * </p>
- *
- * @param s a <code>String</code> value
- * @return a <code>String</code> value
- * @throws NullPointerException if the input string is null.
- */
- public static final String unescapeHTML(String s) {
- return unescapeHTML(s, false);
- }
-
- /**
- * Replace all the occurences of HTML escape strings with the
- * respective characters.
- *
- * @param s a <code>String</code> value
- * @param emulateBrowsers a <code>Boolean</code> value that tells the method
- * to allow entity refs not terminated with a semicolon to be unescaped.
- * (a quirk of this feature, and some browsers, is that an explicit
- * terminating character is needed - e.g., <$ would be unescaped, but
- * not <ab - see the tests for a more in-depth description of browsers)
- * @return a <code>String</code> value
- * @throws NullPointerException if the input string is null.
- */
- public static final String unescapeHTML(String s, boolean emulateBrowsers) {
-
- // See if there are any '&' in the string since that is what we look
- // for to escape. If there isn't, then we don't need to escape this string
- // Based on similar technique used in the escape function.
- int index = s.indexOf('&');
- if (index == -1) {
- // Nothing to escape. Return the original string.
- return s;
- }
-
- // We found an escaped character. Start slow escaping from there.
- char[] chars = s.toCharArray();
- char[] escaped = new char[chars.length];
- System.arraycopy(chars, 0, escaped, 0, index);
-
- // Note: escaped[pos] = end of the escaped char array.
- int pos = index;
-
- for (int i = index; i < chars.length;) {
- if (chars[i] != '&') {
- escaped[pos++] = chars[i++];
- continue;
- }
-
- // Allow e.g. {
- int j = i + 1;
- boolean isNumericEntity = false;
- if (j < chars.length && chars[j] == '#') {
- j++;
- isNumericEntity = true;
- }
-
- // if it's numeric, also check for hex
- boolean isHexEntity = false;
- if (j < chars.length && (chars[j] == 'x' || chars[j] == 'X')) {
- j++;
- isHexEntity = true;
- }
-
- // Scan until we find a char that is not valid for this sequence.
- for (; j < chars.length; j++) {
- char ch = chars[j];
- boolean isDigit = Character.isDigit(ch);
- if (isNumericEntity) {
- // non-hex numeric sequence end condition
- if (!isHexEntity && !isDigit) {
- break;
- }
- // hex sequence end contition
- if (isHexEntity && !isDigit && !HEX_LETTERS.contains(ch)) {
- break;
- }
- }
- // anything other than a digit or letter is always an end condition
- if (!isDigit && !Character.isLetter(ch)) {
- break;
- }
- }
-
- boolean replaced = false;
- if ((j <= chars.length && emulateBrowsers) ||
- (j < chars.length && chars[j] == ';')) {
- // Check for &#D; and 
 pattern
- if (i + 2 < chars.length && s.charAt(i + 1) == '#') {
- try {
- long charcode = 0;
- char ch = s.charAt(i + 2);
- if (isHexEntity) {
- charcode = Long.parseLong(
- new String(chars, i + 3, j - i - 3), 16);
- } else if (Character.isDigit(ch)) {
- charcode = Long.parseLong(
- new String(chars, i + 2, j - i - 2));
- }
- if (charcode > 0 && charcode < 65536) {
- escaped[pos++] = (char) charcode;
- replaced = true;
- }
- } catch (NumberFormatException ex) {
- // Failed, not replaced.
- }
- } else {
- String key = new String(chars, i, j - i);
- Character repl = ESCAPE_STRINGS.get(key);
- if (repl != null) {
- escaped[pos++] = repl;
- replaced = true;
- }
- }
- // Skip over ';'
- if (j < chars.length && chars[j] == ';') {
- j++;
- }
- }
-
- if (!replaced) {
- // Not a recognized escape sequence, leave as-is
- System.arraycopy(chars, i, escaped, pos, j - i);
- pos += j - i;
- }
- i = j;
- }
- return new String(escaped, 0, pos);
- }
-
- // Escaper for < and > only.
- private static final CharEscaper LT_GT_ESCAPE =
- new CharEscaperBuilder()
- .addEscape('<', "<")
- .addEscape('>', ">")
- .toEscaper();
-
- private static final Pattern htmlTagPattern =
- Pattern.compile("</?[a-zA-Z][^>]*>");
-
- /**
- * Given a <code>String</code>, returns an equivalent <code>String</code> with
- * all HTML tags stripped. Note that HTML entities, such as "&amp;" will
- * still be preserved.
- */
- public static String stripHtmlTags(String string) {
- if ((string == null) || "".equals(string)) {
- return string;
- }
- String stripped = htmlTagPattern.matcher(string).replaceAll("");
- /*
- * Certain inputs result in a well-formed HTML:
- * <<X>script>alert(0)<</X>/script> results in <script>alert(0)</script>
- * The following step ensures that no HTML can slip through by replacing all
- * < and > characters with < and > after HTML tags were stripped.
- */
- return LT_GT_ESCAPE.escape(stripped);
- }
-
- /**
- * We escape some characters in s to be able to insert strings into JavaScript
- * code. Also, make sure that we don't write out {@code -->} or
- * {@code </script>}, which may close a script tag, or any char in ["'>] which
- * might close a tag or attribute if seen inside an attribute.
- */
- public static String javaScriptEscape(CharSequence s) {
- return javaScriptEscapeHelper(s, false);
- }
-
- /**
- * We escape some characters in s to be able to insert strings into JavaScript
- * code. Also, make sure that we don't write out {@code -->} or
- * {@code </script>}, which may close a script tag, or any char in ["'>] which
- * might close a tag or attribute if seen inside an attribute.
- * Turns all non-ascii characters into ASCII javascript escape sequences
- * (eg \\uhhhh or \ooo).
- */
- public static String javaScriptEscapeToAscii(CharSequence s) {
- return javaScriptEscapeHelper(s, true);
- }
-
- /**
- * Represents the type of javascript escaping to perform. Each enum below
- * determines whether to use octal escapes and how to handle quotes.
- */
- public static enum JsEscapingMode {
- /** No octal escapes, pass-through ', and escape " as \". */
- JSON,
-
- /** Octal escapes, escapes ' and " to \42 and \47, respectively. */
- EMBEDDABLE_JS,
-
- /** Octal escapes, escapes ' and " to \' and \". */
- MINIMAL_JS
- }
-
- /**
- * Helper for javaScriptEscape and javaScriptEscapeToAscii
- */
- private static String javaScriptEscapeHelper(CharSequence s,
- boolean escapeToAscii) {
- StringBuilder sb = new StringBuilder(s.length() * 9 / 8);
- try {
- escapeStringBody(s, escapeToAscii, JsEscapingMode.EMBEDDABLE_JS, sb);
- } catch (IOException ex) {
- // StringBuilder.append does not throw IOExceptions.
- throw new RuntimeException(ex);
- }
- return sb.toString();
- }
-
- /**
- * Appends the javascript string literal equivalent of plainText to the given
- * out buffer.
- * @param plainText the string to escape.
- * @param escapeToAscii true to encode all characters not in ascii [\x20-\x7e]
- * <br>
- * Full escaping of unicode entites isn't required but this makes
- * sure that unicode strings will survive regardless of the
- * content-encoding of the javascript file which is important when
- * we use this function to autogenerated javascript source files.
- * This is disabled by default because it makes non-latin strings very long.
- * <br>
- * If you seem to have trouble with character-encodings, maybe
- * turn this on to see if the problem goes away. If so, you need
- * to specify a character encoding for your javascript somewhere.
- * @param jsEscapingMode determines the type of escaping to perform.
- * @param out the buffer to append output to.
- */
- /*
- * To avoid fallthrough, we would have to either use a hybrid switch-case/if
- * approach (which would obscure our special handling for ' and "), duplicate
- * the content of the default case, or pass a half-dozen parameters to a
- * helper method containing the code from the default case.
- */
- @SuppressWarnings("fallthrough")
- public static void escapeStringBody(
- CharSequence plainText, boolean escapeToAscii,
- JsEscapingMode jsEscapingMode, Appendable out)
- throws IOException {
- int pos = 0; // Index just past the last char in plainText written to out.
- int len = plainText.length();
- for (int codePoint, charCount, i = 0; i < len; i += charCount) {
- codePoint = Character.codePointAt(plainText, i);
- charCount = Character.charCount(codePoint);
-
- if (!shouldEscapeChar(codePoint, escapeToAscii, jsEscapingMode)) {
- continue;
- }
-
- out.append(plainText, pos, i);
- pos = i + charCount;
- switch (codePoint) {
- case '\b': out.append("\\b"); break;
- case '\t': out.append("\\t"); break;
- case '\n': out.append("\\n"); break;
- case '\f': out.append("\\f"); break;
- case '\r': out.append("\\r"); break;
- case '\\': out.append("\\\\"); break;
- case '"': case '\'':
- if (jsEscapingMode == JsEscapingMode.JSON && '\'' == codePoint) {
- // JSON does not escape a single quote (and it should be surrounded
- // by double quotes).
- out.append((char) codePoint);
- break;
- } else if (jsEscapingMode != JsEscapingMode.EMBEDDABLE_JS) {
- out.append('\\').append((char) codePoint);
- break;
- }
- // fall through
- default:
- if (codePoint >= 0x100 || jsEscapingMode == JsEscapingMode.JSON) {
- appendHexJavaScriptRepresentation(codePoint, out);
- } else {
- // Output the minimal octal encoding. We can't use an encoding
- // shorter than three digits if the next digit is a valid octal
- // digit.
- boolean pad = i + charCount >= len
- || isOctal(plainText.charAt(i + charCount));
- appendOctalJavaScriptRepresentation((char) codePoint, pad, out);
- }
- break;
- }
- }
- out.append(plainText, pos, len);
- }
-
- /**
- * Helper for escapeStringBody, which decides whether to escape a character.
- */
- private static boolean shouldEscapeChar(int codePoint,
- boolean escapeToAscii, JsEscapingMode jsEscapingMode) {
- // If non-ASCII chars should be escaped, identify non-ASCII code points.
- if (escapeToAscii && (codePoint < 0x20 || codePoint > 0x7e)) {
- return true;
- }
-
- // If in JSON escaping mode, check JSON *and* JS escaping rules. The JS
- // escaping rules will escape more characters than needed for JSON,
- // but it is safe to escape any character in JSON.
- // TODO(bbavar): Remove unnecessary escaping for JSON, as long as it can be
- // shown that this change in legacy behavior is safe.
- if (jsEscapingMode == JsEscapingMode.JSON) {
- return mustEscapeCharInJsonString(codePoint)
- || mustEscapeCharInJsString(codePoint);
- }
-
- // Finally, just check the default JS escaping rules.
- return mustEscapeCharInJsString(codePoint);
- }
-
- /**
- * Returns a javascript representation of the character in a hex escaped
- * format.
- *
- * @param codePoint The codepoint to append.
- * @param out The buffer to which the hex representation should be appended.
- */
- private static void appendHexJavaScriptRepresentation(
- int codePoint, Appendable out)
- throws IOException {
- if (Character.isSupplementaryCodePoint(codePoint)) {
- // Handle supplementary unicode values which are not representable in
- // javascript. We deal with these by escaping them as two 4B sequences
- // so that they will round-trip properly when sent from java to javascript
- // and back.
- char[] surrogates = Character.toChars(codePoint);
- appendHexJavaScriptRepresentation(surrogates[0], out);
- appendHexJavaScriptRepresentation(surrogates[1], out);
- return;
- }
- out.append("\\u")
- .append(HEX_CHARS[(codePoint >>> 12) & 0xf])
- .append(HEX_CHARS[(codePoint >>> 8) & 0xf])
- .append(HEX_CHARS[(codePoint >>> 4) & 0xf])
- .append(HEX_CHARS[codePoint & 0xf]);
- }
-
- /**
- * Returns a javascript representation of the character in a hex escaped
- * format. Although this is a rather specific method, it is made public
- * because it is also used by the JSCompiler.
- *
- * @param ch The character to append.
- * @param pad true to force use of the full 3 digit representation.
- * @param out The buffer to which the hex representation should be appended.
- */
- private static void appendOctalJavaScriptRepresentation(
- char ch, boolean pad, Appendable out) throws IOException {
- if (ch >= 0100
- // Be paranoid at the end of a string since someone might call
- // this method again with another string segment.
- || pad) {
- out.append('\\')
- .append(OCTAL_CHARS[(ch >>> 6) & 0x7])
- .append(OCTAL_CHARS[(ch >>> 3) & 0x7])
- .append(OCTAL_CHARS[ch & 0x7]);
- } else if (ch >= 010) {
- out.append('\\')
- .append(OCTAL_CHARS[(ch >>> 3) & 0x7])
- .append(OCTAL_CHARS[ch & 0x7]);
- } else {
- out.append('\\')
- .append(OCTAL_CHARS[ch & 0x7]);
- }
- }
-
- /**
- * Although this is a rather specific method, it is made public
- * because it is also used by the JSCompiler.
- *
- * @see #appendHexJavaScriptRepresentation(int, Appendable)
- */
- public static void appendHexJavaScriptRepresentation(StringBuilder sb,
- char c) {
- try {
- appendHexJavaScriptRepresentation(c, sb);
- } catch (IOException ex) {
- // StringBuilder does not throw IOException.
- throw new RuntimeException(ex);
- }
- }
-
- /**
- * Undo escaping as performed in javaScriptEscape(.)
- * Throws an IllegalArgumentException if the string contains
- * bad escaping.
- */
- public static String javaScriptUnescape(String s) {
- StringBuilder sb = new StringBuilder(s.length());
- for (int i = 0; i < s.length(); ) {
- char c = s.charAt(i);
- if (c == '\\') {
- i = javaScriptUnescapeHelper(s, i + 1, sb);
- } else {
- sb.append(c);
- i++;
- }
- }
- return sb.toString();
- }
-
- /**
- * Looks for an escape code starting at index i of s,
- * and appends it to sb.
- * @return the index of the first character in s
- * after the escape code.
- * @throws IllegalArgumentException if the escape code
- * is invalid
- */
- private static int javaScriptUnescapeHelper(String s, int i,
- StringBuilder sb) {
- if (i >= s.length()) {
- throw new IllegalArgumentException(
- "End-of-string after escape character in [" + s + "]");
- }
-
- char c = s.charAt(i++);
- switch (c) {
- case 'n': sb.append('\n'); break;
- case 'r': sb.append('\r'); break;
- case 't': sb.append('\t'); break;
- case 'b': sb.append('\b'); break;
- case 'f': sb.append('\f'); break;
- case '\\':
- case '\"':
- case '\'':
- case '>':
- sb.append(c);
- break;
- case '0': case '1': case '2': case '3':
- case '4': case '5': case '6': case '7':
- --i; // backup to first octal digit
- int nOctalDigits = 1;
- int digitLimit = c < '4' ? 3 : 2;
- while (nOctalDigits < digitLimit && i + nOctalDigits < s.length()
- && isOctal(s.charAt(i + nOctalDigits))) {
- ++nOctalDigits;
- }
- sb.append(
- (char) Integer.parseInt(s.substring(i, i + nOctalDigits), 8));
- i += nOctalDigits;
- break;
- case 'x':
- case 'u':
- String hexCode;
- int nHexDigits = (c == 'u' ? 4 : 2);
- try {
- hexCode = s.substring(i, i + nHexDigits);
- } catch (IndexOutOfBoundsException ioobe) {
- throw new IllegalArgumentException(
- "Invalid unicode sequence [" + s.substring(i) + "] at index " + i
- + " in [" + s + "]");
- }
- int unicodeValue;
- try {
- unicodeValue = Integer.parseInt(hexCode, 16);
- } catch (NumberFormatException nfe) {
- throw new IllegalArgumentException(
- "Invalid unicode sequence [" + hexCode + "] at index " + i +
- " in [" + s + "]");
- }
- sb.append((char) unicodeValue);
- i += nHexDigits;
- break;
- default:
- throw new IllegalArgumentException(
- "Unknown escape code [" + c + "] at index " + i + " in [" + s + "]"
- );
- }
-
- return i;
- }
-
- // C0 control characters except \t, \n, and \r and 0xFFFE and 0xFFFF
- private static final CharMatcher CONTROL_MATCHER = CharMatcher.anyOf(
- "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007" +
- "\u0008\u000B\u000C\u000E\u000F" +
- "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017" +
- "\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" +
- "\uFFFE\uFFFF");
-
- /**
- * Escape a string that is meant to be embedded in a CDATA section.
- * The returned string is guaranteed to be valid CDATA content.
- * The syntax of CDATA sections is the following:
- * <blockquote>
- * <code><[!CDATA[...]]></code>
- * </blockquote>
- * The only invalid character sequence in a CDATA tag is "]]>".
- * If this sequence is present in the input string, we replace
- * it by closing the current CDATA field, then write ']]&gt;',
- * then reopen a new CDATA section.
- */
- public static String xmlCDataEscape(String s) {
- // Make sure there are no illegal control characters.
- s = CONTROL_MATCHER.removeFrom(s);
- // Return the original reference if the string doesn't have a match.
- int found = s.indexOf("]]>");
- if (found == -1) {
- return s;
- }
-
- // For each occurrence of "]]>", append a string that adds "]]>" after
- // the end of the CDATA which has just been closed, then opens a new CDATA.
- StringBuilder sb = new StringBuilder();
- int prev = 0;
- do {
- sb.append(s.substring(prev, found + 3));
- sb.append("]]><![CDATA[");
- prev = found + 3;
- } while ((found = s.indexOf("]]>", prev)) != -1);
- sb.append(s.substring(prev));
- return sb.toString();
- }
-
- /**
- * We escape some characters in s to be able to insert strings into Java code
- *
- * @deprecated Use {@link CharEscapers#asciiHtmlEscaper()} and {@link
- * CharEscapers#javaCharEscaper()} or {@link CharEscapers#javaStringEscaper()}
- * instead. This method combines two forms of escaping in a way that's rarely
- * desired.
- */
- @Deprecated
- public static String javaEscape(String s) {
- return JAVA_ESCAPE.escape(s);
- }
-
- // Java escaper.
- private static final CharEscaper JAVA_ESCAPE =
- new CharEscaperBuilder()
- .addEscape('\n', "\\n")
- .addEscape('\r', "\\r")
- .addEscape('\t', "\\t")
- .addEscape('\\', "\\\\")
- .addEscape('\"', "\\\"")
- .addEscape('&', "&")
- .addEscape('<', "<")
- .addEscape('>', ">")
- .addEscape('\'', "\\\'")
- .toEscaper();
-
- /**
- * Escapes the special characters from a string so it can be used as part of
- * a regex pattern. This method is for use on gnu.regexp style regular
- * expressions.
- *
- * @deprecated Use {@link Pattern#quote(String)} instead. Note that it may not
- * be compatible with gnu.regexp style regular expressions.
- */
- @Deprecated
- public static String regexEscape(String s) {
- return REGEX_ESCAPE.escape(s);
- }
-
- // Regex escaper escapes all regex characters.
- private static final CharEscaper REGEX_ESCAPE =
- new CharEscaperBuilder()
- .addEscape('(', "\\(")
- .addEscape(')', "\\)")
- .addEscape('|', "\\|")
- .addEscape('*', "\\*")
- .addEscape('+', "\\+")
- .addEscape('?', "\\?")
- .addEscape('.', "\\.")
- .addEscape('{', "\\{")
- .addEscape('}', "\\}")
- .addEscape('[', "\\[")
- .addEscape(']', "\\]")
- .addEscape('$', "\\$")
- .addEscape('^', "\\^")
- .addEscape('\\', "\\\\")
- .toEscaper();
-
- /**
- * If you want to preserve the exact
- * current (odd) behavior when {@code doStrip} is {@code true}, use
- * {@code .trimResults(CharMatcher.LEGACY_WHITESPACE).omitEmptyStrings()} on
- * the splitter.
- *
- * @param in what to process
- * @param delimiter the delimiting string
- * @return the tokens
- * @deprecated see the detailed instructions under
- * {@link #split(String, String, boolean)}
- */
- @Deprecated
- public static LinkedList<String> string2List(
- String in, String delimiter, boolean doStrip) {
- if (in == null) {
- return null;
- }
-
- LinkedList<String> out = new LinkedList<String>();
- string2Collection(in, delimiter, doStrip, out);
- return out;
- }
-
- /**
- * See the detailed instructions under {@link
- * #split(String, String, boolean)}. Pass the resulting {@code Iterable} to
- * {@link com.google.common.collect.Sets#newHashSet(Iterable)}. If you want to
- * preserve the exact current (odd) behavior when {@code doStrip} is {@code
- * true}, use {@code
- * .trimResults(CharMatcher.LEGACY_WHITESPACE).omitEmptyStrings()} on the
- * splitter.
- *
- * @param in what to process
- * @param delimiter the delimiting string
- * @param doStrip to strip the substrings before adding to the list
- * @return the tokens
- * @deprecated see the detailed instructions under
- * {@link #split(String, String, boolean)}
- */
- @Deprecated
- public static Set<String> string2Set(
- String in, String delimiter, boolean doStrip) {
- if (in == null) {
- return null;
- }
-
- HashSet<String> out = new HashSet<String>();
- string2Collection(in, delimiter, doStrip, out);
- return out;
- }
-
- /**
- * See the detailed instructions under {@link
- * #split(String, String, boolean)}. If you want to preserve the exact current
- * (odd) behavior when {@code doStrip} is {@code true}, use {@code
- * .trimResults(CharMatcher.LEGACY_WHITESPACE).omitEmptyStrings()} on the
- * splitter.
- *
- * @param in The delimited input string to process
- * @param delimiter The string delimiting entries in the input string.
- * @param doStrip whether to strip the substrings before adding to the
- * collection
- * @param collection The collection to which the strings will be added. If
- * <code>null</code>, a new <code>List</code> will be created.
- * @return The collection to which the substrings were added. This is
- * syntactic sugar to allow call chaining.
- * @deprecated see the detailed instructions under
- * {@link #split(String, String, boolean)}
- */
- @Deprecated
- public static Collection<String> string2Collection(
- String in,
- String delimiter,
- boolean doStrip,
- Collection<String> collection) {
- if (in == null) {
- return null;
- }
- if (collection == null) {
- collection = new ArrayList<String>();
- }
- if (delimiter == null || delimiter.length() == 0) {
- collection.add(in);
- return collection;
- }
-
- int fromIndex = 0;
- int pos;
- while ((pos = in.indexOf(delimiter, fromIndex)) >= 0) {
- String interim = in.substring(fromIndex, pos);
- if (doStrip) {
- interim = strip(interim);
- }
- if (!doStrip || interim.length() > 0) {
- collection.add(interim);
- }
-
- fromIndex = pos + delimiter.length();
- }
-
- String interim = in.substring(fromIndex);
- if (doStrip) {
- interim = strip(interim);
- }
- if (!doStrip || interim.length() > 0) {
- collection.add(interim);
- }
-
- return collection;
- }
-
- /**
- * This converts a string to a Map. It will first split the string into
- * entries using delimEntry. Then each entry is split into a key and a value
- * using delimKey. By default we strip the keys. Use doStripEntry to strip
- * also the entries.
- *
- * Note that this method returns a {@link HashMap}, which means that entries
- * will be in no particular order. See {@link #stringToOrderedMap}.
- *
- * @param in the string to be processed
- * @param delimEntry delimiter for the entries
- * @param delimKey delimiter between keys and values
- * @param doStripEntry strip entries before inserting in the map
- *
- * @return HashMap
- */
- public static HashMap<String, String> string2Map(
- String in, String delimEntry, String delimKey,
- boolean doStripEntry) {
- if (in == null) {
- return null;
- }
-
- return stringToMapImpl(new HashMap<String, String>(), in, delimEntry,
- delimKey, doStripEntry);
- }
-
- /**
- * This converts a string to a Map, with entries in the same order as the
- * key/value pairs in the input string. It will first split the string into
- * entries using delimEntry. Then each entry is split into a key and a value
- * using delimKey. By default we strip the keys. Use doStripEntry to strip
- * also the entries.
- *
- * @param in the string to be processed
- * @param delimEntry delimiter for the entries
- * @param delimKey delimiter between keys and values
- * @param doStripEntry strip entries before inserting in the map
- *
- * @return key/value pairs as a Map, in order
- */
- public static Map<String, String> stringToOrderedMap(
- String in, String delimEntry, String delimKey,
- boolean doStripEntry) {
- if (in == null) {
- return null;
- }
-
- return stringToMapImpl(new LinkedHashMap<String, String>(), in, delimEntry,
- delimKey, doStripEntry);
- }
-
- /**
- * This adds key/value pairs from the given string to the given Map.
- * It will first split the string into entries using delimEntry. Then each
- * entry is split into a key and a value using delimKey. By default we
- * strip the keys. Use doStripEntry to strip also the entries.
- *
- * @param out - Map to output into
- * @param in - the string to be processed
- * @param delimEntry - delimiter for the entries
- * @param delimKey - delimiter between keys and values
- * @param doStripEntry - strip entries before inserting in the map
- * @return out, for caller's convenience
- */
- private static <T extends Map<String, String>> T stringToMapImpl(T out,
- String in, String delimEntry, String delimKey, boolean doStripEntry) {
-
- if (isEmpty(delimEntry) || isEmpty(delimKey)) {
- out.put(strip(in), "");
- return out;
- }
-
- Iterator<String> it = string2List(in, delimEntry, false).iterator();
- int len = delimKey.length();
- while (it.hasNext()) {
- String entry = it.next();
- int pos = entry.indexOf(delimKey);
- if (pos > 0) {
- String value = entry.substring(pos + len);
- if (doStripEntry) {
- value = strip(value);
- }
- out.put(strip(entry.substring(0, pos)), value);
- } else {
- out.put(strip(entry), "");
- }
- }
-
- return out;
- }
-
- /**
- * This function concatenates the elements of a Map in a string with form
- * "<key1><sepKey><value1><sepEntry>...<keyN><sepKey><valueN>"
- *
- * @param in - the map to be converted
- * @param sepKey - the separator to put between key and value
- * @param sepEntry - the separator to put between map entries
- * @return String
- * @deprecated create a {@link MapJoiner}, for example {@code
- * Joiner.on(sepEntry).withKeyValueSeparator(sepKey)}. Ensure that your
- * map is non-null and use this map joiner's {@link MapJoiner#join(Map)}
- * method. To preserve behavior exactly, just in-line this method call.
- */
- @Deprecated public static <K, V> String map2String(
- Map<K, V> in, String sepKey, String sepEntry) {
- return (in == null) ? null : Joiner
- .on(sepEntry)
- .useForNull("null")
- .withKeyValueSeparator(sepKey)
- .join(in);
- }
-
- /**
- * Given a map, creates and returns a new map in which all keys are the
- * lower-cased version of each key.
- *
- * @param map A map containing String keys to be lowercased
- * @throws IllegalArgumentException if the map contains duplicate string keys
- * after lower casing
- */
- public static <V> Map<String, V> lowercaseKeys(Map<String, V> map) {
- Map<String, V> result = new HashMap<String, V>(map.size());
- for (Map.Entry<String, V> entry : map.entrySet()) {
- String key = entry.getKey();
- if (result.containsKey(key.toLowerCase())) {
- throw new IllegalArgumentException(
- "Duplicate string key in map when lower casing");
- }
- result.put(key.toLowerCase(), entry.getValue());
- }
- return result;
- }
-
- /**
- * Replaces any string of adjacent whitespace characters with the whitespace
- * character " ".
- *
- * @param str the string you want to munge
- * @return String with no more excessive whitespace!
- * @deprecated ensure the string is not null and use {@code
- * CharMatcher.LEGACY_WHITESPACE.collapseFrom(str, ' ')}; also consider
- * whether you really want the legacy whitespace definition, or something
- * more standard like {@link CharMatcher#WHITESPACE}.
- */
- @Deprecated public static String collapseWhitespace(String str) {
- return (str == null) ? null
- : CharMatcher.LEGACY_WHITESPACE.collapseFrom(str, ' ');
- }
-
- /**
- * Replaces any string of matched characters with the supplied string.<p>
- *
- * This is a more general version of collapseWhitespace.
- *
- * <pre>
- * E.g. collapse("hello world", " ", "::")
- * will return the following string: "hello::world"
- * </pre>
- *
- * @param str the string you want to munge
- * @param chars all of the characters to be considered for munge
- * @param replacement the replacement string
- * @return munged and replaced string.
- * @deprecated if {@code replacement} is the empty string, use {@link
- * CharMatcher#removeFrom(CharSequence)}; if it is a single character,
- * use {@link CharMatcher#collapseFrom(CharSequence, char)}; for longer
- * replacement strings use {@link String#replaceAll(String, String)} with
- * a regular expression that matches one or more occurrences of {@code
- * chars}. In all cases you must first ensure that {@code str} is not
- * null.
- */
- @Deprecated public static String collapse(
- String str, String chars, String replacement) {
- if (str == null) {
- return null;
- }
-
- StringBuilder newStr = new StringBuilder();
-
- boolean prevCharMatched = false;
- char c;
- for (int i = 0; i < str.length(); i++) {
- c = str.charAt(i);
- if (chars.indexOf(c) != -1) {
- // this character is matched
- if (prevCharMatched) {
- // apparently a string of matched chars, so don't append anything
- // to the string
- continue;
- }
- prevCharMatched = true;
- newStr.append(replacement);
- } else {
- prevCharMatched = false;
- newStr.append(c);
- }
- }
-
- return newStr.toString();
- }
-
- /**
- * Returns a string with all sequences of ISO control chars (0x00 to 0x1F and
- * 0x7F to 0x9F) replaced by the supplied string. ISO control characters are
- * identified via {@link Character#isISOControl(char)}.
- *
- * @param str the string you want to strip of ISO control chars
- * @param replacement the replacement string
- * @return a String with all control characters replaced by the replacement
- * string, or null if input is null.
- * @deprecated use {@link CharMatcher#JAVA_ISO_CONTROL}. If {@code
- * replacement} is the empty string, use {@link
- * CharMatcher#removeFrom(CharSequence)}; if it is a single character,
- * use {@link CharMatcher#collapseFrom(CharSequence, char)}; for longer
- * replacement strings use
- * {@code str.replaceAll("\p{Cntrl}+", replacement)}.
- * In all cases you must first ensure that {@code str} is not null.
- */
- @Deprecated public static String collapseControlChars(
- String str, String replacement) {
- /*
- * We re-implement the StringUtil.collapse() loop here rather than call
- * collapse() with an input String of control chars, because matching via
- * isISOControl() is about 10x faster.
- */
- if (str == null) {
- return null;
- }
-
- StringBuilder newStr = new StringBuilder();
-
- boolean prevCharMatched = false;
- char c;
- for (int i = 0; i < str.length(); i++) {
- c = str.charAt(i);
- if (Character.isISOControl(c)) {
- // this character is matched
- if (prevCharMatched) {
- // apparently a string of matched chars, so don't append anything
- // to the string
- continue;
- }
- prevCharMatched = true;
- newStr.append(replacement);
- } else {
- prevCharMatched = false;
- newStr.append(c);
- }
- }
-
- return newStr.toString();
- }
-
- /**
- * Read a String of up to maxLength bytes from an InputStream.
- *
- * <p>Note that this method uses the default platform encoding, and expects
- * that encoding to be single-byte, which is not always the case. Its use
- * is discouraged. For reading the entire stream (maxLength == -1) you can use:
- * <pre>
- * CharStreams.toString(new InputStreamReader(is, Charsets.ISO_8859_1))
- * </pre>
- * {@code CharStreams} is in the {@code com.google.common.io} package.
- *
- * <p>For maxLength >= 0 a literal translation would be
- * <pre>
- * CharStreams.toString(new InputStreamReader(
- * new LimitInputStream(is, maxLength), Charsets.ISO_8859_1))
- * </pre>
- * For multi-byte encodings that is broken because the limit could end in
- * the middle of the character--it would be better to limit the reader than
- * the underlying stream.
- *
- * @param is input stream
- * @param maxLength max number of bytes to read from "is". If this is -1, we
- * read everything.
- *
- * @return String up to maxLength bytes, read from "is"
- * @deprecated see the advice above
- */
- @Deprecated public static String stream2String(InputStream is, int maxLength)
- throws IOException {
- byte[] buffer = new byte[4096];
- StringWriter sw = new StringWriter();
- int totalRead = 0;
- int read = 0;
-
- do {
- sw.write(new String(buffer, 0, read));
- totalRead += read;
- read = is.read(buffer, 0, buffer.length);
- } while (((-1 == maxLength) || (totalRead < maxLength)) && (read != -1));
-
- return sw.toString();
- }
-
- /**
- * Parse a list of substrings separated by a given delimiter. The delimiter
- * can also appear in substrings (just double them):
- *
- * parseDelimitedString("this|is", '|') returns ["this","is"]
- * parseDelimitedString("this||is", '|') returns ["this|is"]
- *
- * @param list String containing delimited substrings
- * @param delimiter Delimiter (anything except ' ' is allowed)
- *
- * @return String[] A String array of parsed substrings
- */
- public static String[] parseDelimitedList(String list,
- char delimiter) {
- String delim = "" + delimiter;
- // Append a sentinel of delimiter + space
- // (see comments below for more info)
- StringTokenizer st = new StringTokenizer(list + delim + " ",
- delim,
- true);
- ArrayList<String> v = new ArrayList<String>();
- String lastToken = "";
- StringBuilder word = new StringBuilder();
-
- // We keep a sliding window of 2 tokens
- //
- // delimiter : delimiter -> append delimiter to current word
- // and clear most recent token
- // (so delim : delim : delim will not
- // be treated as two escaped delims.)
- //
- // tok : delimiter -> append tok to current word
- //
- // delimiter : tok -> add current word to list, and clear it.
- // (We append a sentinel that conforms to this
- // pattern to make sure we've pushed every parsed token)
- while (st.hasMoreTokens()) {
- String tok = st.nextToken();
- if (lastToken != null) {
- if (tok.equals(delim)) {
- word.append(lastToken);
- if (lastToken.equals(delim)) { tok = null; }
- } else {
- if (word.length() != 0) {
- v.add(word.toString());
- }
- word.setLength(0);
- }
- }
- lastToken = tok;
- }
-
- return v.toArray(new String[0]);
- }
-
- /**
- * Compares two strings, guarding against nulls.
- *
- * @param nullsAreGreater true if nulls should be greater than any string,
- * false is less than.
- * @deprecated use {@link String#CASE_INSENSITIVE_ORDER}, together with
- * {@link com.google.common.collect.Ordering#nullsFirst()} or
- * {@link com.google.common.collect.Ordering#nullsLast()} if
- * needed
- */
- @Deprecated public static int compareToIgnoreCase(String s1, String s2,
- boolean nullsAreGreater) {
- if (s1 == s2) {
- return 0; // Either both the same String, or both null
- }
- if (s1 == null) {
- return nullsAreGreater ? 1 : -1;
- }
- if (s2 == null) {
- return nullsAreGreater ? -1 : 1;
- }
- return s1.compareToIgnoreCase(s2);
- }
-
- /**
- * Splits s with delimiters in delimiter and returns the last token
- */
- public static String lastToken(String s, String delimiter) {
- return s.substring(CharMatcher.anyOf(delimiter).lastIndexIn(s) + 1);
- }
-
- private static final Pattern characterReferencePattern =
- Pattern.compile("&#?[a-zA-Z0-9]{1,8};");
-
- /**
- * Determines if a string contains what looks like an html character
- * reference. Useful for deciding whether unescaping is necessary.
- */
- public static boolean containsCharRef(String s) {
- return characterReferencePattern.matcher(s).find();
- }
-
- /**
- * Determines if a string is a Hebrew word. A string is considered to be
- * a Hebrew word if {@link #isHebrew(int)} is true for any of its characters.
- */
- public static boolean isHebrew(String s) {
- int len = s.length();
- for (int i = 0; i < len; ++i) {
- if (isHebrew(s.codePointAt(i))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Determines if a character is a Hebrew character.
- */
- public static boolean isHebrew(int codePoint) {
- return Character.UnicodeBlock.HEBREW.equals(
- Character.UnicodeBlock.of(codePoint));
- }
-
- /**
- * Determines if a string is a CJK word. A string is considered to be CJK
- * if {@link #isCjk(char)} is true for any of its characters.
- */
- public static boolean isCjk(String s) {
- int len = s.length();
- for (int i = 0; i < len; ++i) {
- if (isCjk(s.codePointAt(i))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Unicode code blocks containing CJK characters.
- */
- private static final Set<Character.UnicodeBlock> CJK_BLOCKS;
- static {
- Set<Character.UnicodeBlock> set = new HashSet<Character.UnicodeBlock>();
- set.add(Character.UnicodeBlock.HANGUL_JAMO);
- set.add(Character.UnicodeBlock.CJK_RADICALS_SUPPLEMENT);
- set.add(Character.UnicodeBlock.KANGXI_RADICALS);
- set.add(Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION);
- set.add(Character.UnicodeBlock.HIRAGANA);
- set.add(Character.UnicodeBlock.KATAKANA);
- set.add(Character.UnicodeBlock.BOPOMOFO);
- set.add(Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO);
- set.add(Character.UnicodeBlock.KANBUN);
- set.add(Character.UnicodeBlock.BOPOMOFO_EXTENDED);
- set.add(Character.UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS);
- set.add(Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS);
- set.add(Character.UnicodeBlock.CJK_COMPATIBILITY);
- set.add(Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A);
- set.add(Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS);
- set.add(Character.UnicodeBlock.HANGUL_SYLLABLES);
- set.add(Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS);
- set.add(Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS);
- set.add(Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS);
- set.add(Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B);
- set.add(Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT);
- CJK_BLOCKS = Collections.unmodifiableSet(set);
- }
-
- /**
- * Determines if a character is a CJK ideograph or a character typically
- * used only in CJK text.
- *
- * Note: This function cannot handle supplementary characters. To handle all
- * Unicode characters, including supplementary characters, use the function
- * {@link #isCjk(int)}.
- */
- public static boolean isCjk(char ch) {
- return isCjk((int) ch);
- }
-
- /**
- * Determines if a character is a CJK ideograph or a character typically
- * used only in CJK text.
- */
- public static boolean isCjk(int codePoint) {
- // Time-saving early exit for all Latin-1 characters.
- if ((codePoint & 0xFFFFFF00) == 0) {
- return false;
- }
-
- return CJK_BLOCKS.contains(Character.UnicodeBlock.of(codePoint));
- }
-
- /**
- * Returns the approximate display width of the string, measured in units of
- * ascii characters.
- *
- * @see StringUtil#displayWidth(char)
- */
- public static int displayWidth(String s) {
- // TODO(kevinb): could reimplement this as
- // return s.length() * 2 - CharMatcher.SINGLE_WIDTH.countIn(s);
- int width = 0;
- int len = s.length();
- for (int i = 0; i < len; ++i) {
- width += displayWidth(s.charAt(i));
- }
- return width;
- }
-
- /**
- * Returns the approximate display width of the character, measured
- * in units of ascii characters.
- *
- * This method should err on the side of caution. By default, characters
- * are assumed to have width 2; this covers CJK ideographs, various
- * symbols and miscellaneous weird scripts. Given below are some Unicode
- * ranges for which it seems safe to assume that no character is
- * substantially wider than an ascii character:
- * - Latin, extended Latin, even more extended Latin.
- * - Greek, extended Greek, Cyrillic.
- * - Some symbols (including currency symbols) and punctuation.
- * - Half-width Katakana and Hangul.
- * - Hebrew
- * - Arabic
- * - Thai
- * Characters in these ranges are given a width of 1.
- *
- * IMPORTANT: this function has analogs in C++ (encodingutils.cc,
- * named UnicodeCharWidth) and JavaScript
- * (java/com/google/ads/common/frontend/adwordsbase/resources/CreateAdUtil.js),
- * which need to be updated if you change the implementation here.
- */
- public static int displayWidth(char ch) {
- if (ch <= '\u04f9' || // CYRILLIC SMALL LETTER YERU WITH DIAERESIS
- ch == '\u05be' || // HEBREW PUNCTUATION MAQAF
- (ch >= '\u05d0' && ch <= '\u05ea') || // HEBREW LETTER ALEF ... TAV
- ch == '\u05F3' || // HEBREW PUNCTUATION GERESH
- ch == '\u05f4' || // HEBREW PUNCTUATION GERSHAYIM
- (ch >= '\u0600' && ch <= '\u06ff') || // Block=Arabic
- (ch >= '\u0750' && ch <= '\u077f') || // Block=Arabic_Supplement
- (ch >= '\ufb50' && ch <= '\ufdff') || // Block=Arabic_Presentation_Forms-A
- (ch >= '\ufe70' && ch <= '\ufeff') || // Block=Arabic_Presentation_Forms-B
- (ch >= '\u1e00' && ch <= '\u20af') || /* LATIN CAPITAL LETTER A WITH RING BELOW
- ... DRACHMA SIGN */
- (ch >= '\u2100' && ch <= '\u213a') || // ACCOUNT OF ... ROTATED CAPITAL Q
- (ch >= '\u0e00' && ch <= '\u0e7f') || // Thai
- (ch >= '\uff61' && ch <= '\uffdc')) { /* HALFWIDTH IDEOGRAPHIC FULL STOP
- ... HALFWIDTH HANGUL LETTER I */
- return 1;
- }
- return 2;
- }
-
- /**
- * @return a string representation of the given native array.
- */
- public static String toString(float[] iArray) {
- if (iArray == null) {
- return "NULL";
- }
-
- StringBuilder buffer = new StringBuilder();
- buffer.append("[");
- for (int i = 0; i < iArray.length; i++) {
- buffer.append(iArray[i]);
- if (i != (iArray.length - 1)) {
- buffer.append(", ");
- }
- }
- buffer.append("]");
- return buffer.toString();
- }
-
- /**
- * @return a string representation of the given native array.
- */
- public static String toString(long[] iArray) {
- if (iArray == null) {
- return "NULL";
- }
-
- StringBuilder buffer = new StringBuilder();
- buffer.append("[");
- for (int i = 0; i < iArray.length; i++) {
- buffer.append(iArray[i]);
- if (i != (iArray.length - 1)) {
- buffer.append(", ");
- }
- }
- buffer.append("]");
- return buffer.toString();
- }
-
- /**
- * @return a string representation of the given native array
- */
- public static String toString(int[] iArray) {
- if (iArray == null) {
- return "NULL";
- }
-
- StringBuilder buffer = new StringBuilder();
- buffer.append("[");
- for (int i = 0; i < iArray.length; i++) {
- buffer.append(iArray[i]);
- if (i != (iArray.length - 1)) {
- buffer.append(", ");
- }
- }
- buffer.append("]");
- return buffer.toString();
- }
-
- /**
- * @return a string representation of the given array.
- */
- public static String toString(String[] iArray) {
- if (iArray == null) { return "NULL"; }
-
- StringBuilder buffer = new StringBuilder();
- buffer.append("[");
- for (int i = 0; i < iArray.length; i++) {
- buffer.append("'").append(iArray[i]).append("'");
- if (i != iArray.length - 1) {
- buffer.append(", ");
- }
- }
- buffer.append("]");
-
- return buffer.toString();
- }
-
- /**
- * Returns the string, in single quotes, or "NULL". Intended only for
- * logging.
- *
- * @param s the string
- * @return the string, in single quotes, or the string "null" if it's null.
- */
- public static String toString(String s) {
- if (s == null) {
- return "NULL";
- } else {
- return new StringBuilder(s.length() + 2).append("'").append(s)
- .append("'").toString();
- }
- }
-
- /**
- * @return a string representation of the given native array
- */
- public static String toString(int[][] iArray) {
- if (iArray == null) {
- return "NULL";
- }
-
- StringBuilder buffer = new StringBuilder();
- buffer.append("[");
- for (int i = 0; i < iArray.length; i++) {
- buffer.append("[");
- for (int j = 0; j < iArray[i].length; j++) {
- buffer.append(iArray[i][j]);
- if (j != (iArray[i].length - 1)) {
- buffer.append(", ");
- }
- }
- buffer.append("]");
- if (i != iArray.length - 1) {
- buffer.append(" ");
- }
- }
- buffer.append("]");
- return buffer.toString();
- }
-
- /**
- * @return a string representation of the given native array.
- */
- public static String toString(long[][] iArray) {
- if (iArray == null) { return "NULL"; }
-
- StringBuilder buffer = new StringBuilder();
- buffer.append("[");
- for (int i = 0; i < iArray.length; i++) {
- buffer.append("[");
- for (int j = 0; j < iArray[i].length; j++) {
- buffer.append(iArray[i][j]);
- if (j != (iArray[i].length - 1)) {
- buffer.append(", ");
- }
- }
- buffer.append("]");
- if (i != iArray.length - 1) {
- buffer.append(" ");
- }
- }
- buffer.append("]");
- return buffer.toString();
- }
-
- /**
- * @return a String representation of the given object array.
- * The strings are obtained by calling toString() on the
- * underlying objects.
- */
- public static String toString(Object[] obj) {
- if (obj == null) { return "NULL"; }
- StringBuilder tmp = new StringBuilder();
- tmp.append("[");
- for (int i = 0; i < obj.length; i++) {
- tmp.append(obj[i].toString());
- if (i != obj.length - 1) {
- tmp.append(",");
- }
- }
- tmp.append("]");
- return tmp.toString();
- }
-
- private static final char[] HEX_CHARS
- = { '0', '1', '2', '3', '4', '5', '6', '7',
- '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
- private static final char[] OCTAL_CHARS = HEX_CHARS; // ignore the last 8 :)
-
- /**
- * Convert a byte array to a hex-encoding string: "a33bff00..."
- *
- * @deprecated Use {@link ByteArrays#toHexString}.
- */
- @Deprecated public static String bytesToHexString(final byte[] bytes) {
- return ByteArrays.toHexString(bytes);
- }
-
- /**
- * Convert a byte array to a hex-encoding string with the specified
- * delimiter: "a3<delimiter>3b<delimiter>ff..."
- */
- public static String bytesToHexString(final byte[] bytes,
- Character delimiter) {
- StringBuilder hex =
- new StringBuilder(bytes.length * (delimiter == null ? 2 : 3));
- int nibble1, nibble2;
- for (int i = 0; i < bytes.length; i++) {
- nibble1 = (bytes[i] >>> 4) & 0xf;
- nibble2 = bytes[i] & 0xf;
- if (i > 0 && delimiter != null) { hex.append(delimiter.charValue()); }
- hex.append(HEX_CHARS[nibble1]);
- hex.append(HEX_CHARS[nibble2]);
- }
- return hex.toString();
- }
-
- /**
- * Safely convert the string to uppercase.
- * @return upper case representation of the String; or null if
- * the input string is null.
- */
- public static String toUpperCase(String src) {
- if (src == null) {
- return null;
- } else {
- return src.toUpperCase();
- }
- }
-
- /**
- * Safely convert the string to lowercase.
- * @return lower case representation of the String; or null if
- * the input string is null.
- */
- public static String toLowerCase(String src) {
- if (src == null) {
- return null;
- } else {
- return src.toLowerCase();
- }
- }
-
- private static final Pattern dbSpecPattern =
- Pattern.compile("(.*)\\{(\\d+),(\\d+)\\}(.*)");
-
- /**
- * @param dbSpecComponent a single component of a DBDescriptor spec
- * (e.g. the host or database component). The expected format of the string is:
- * <br>
- * <center>(prefix){(digits),(digits)}(suffix)</center>
- * </br>
- * @return a shard expansion of the given String.
- * Note that unless the pattern is matched exactly, no expansion is
- * performed and the original string is returned unaltered.
- * For example, 'db{0,1}.adz' is expanded into 'db0.adz, db1.adz'.
- * Note that this method is added to StringUtil instead of
- * DBDescriptor to better encapsulate the choice of regexp implementation.
- * @throws IllegalArgumentException if the string does not parse.
- */
- public static String expandShardNames(String dbSpecComponent)
- throws IllegalArgumentException, IllegalStateException {
-
- Matcher matcher = dbSpecPattern.matcher(dbSpecComponent);
- if (matcher.find()) {
- try {
- String prefix = dbSpecComponent.substring(
- matcher.start(1), matcher.end(1));
- int minShard =
- Integer.parseInt(
- dbSpecComponent.substring(
- matcher.start(2), matcher.end(2)));
- int maxShard =
- Integer.parseInt(
- dbSpecComponent.substring(
- matcher.start(3), matcher.end(3)));
- String suffix = dbSpecComponent.substring(
- matcher.start(4), matcher.end(4));
- //Log2.logEvent(prefix + " " + minShard + " " + maxShard + " " + suffix);
- if (minShard > maxShard) {
- throw new IllegalArgumentException(
- "Maximum shard must be greater than or equal to " +
- "the minimum shard");
- }
- StringBuilder tmp = new StringBuilder();
- for (int shard = minShard; shard <= maxShard; shard++) {
- tmp.append(prefix).append(shard).append(suffix);
- if (shard != maxShard) {
- tmp.append(",");
- }
- }
- return tmp.toString();
- } catch (NumberFormatException nfex) {
- throw new IllegalArgumentException(
- "Malformed DB specification component: " + dbSpecComponent);
- }
- } else {
- return dbSpecComponent;
- }
- }
-
-
- /**
- * Returns a string that is equivalent to the specified string with its
- * first character converted to uppercase as by {@link String#toUpperCase()}.
- * The returned string will have the same value as the specified string if
- * its first character is non-alphabetic, if its first character is already
- * uppercase, or if the specified string is of length 0.
- *
- * <p>For example:
- * <pre>
- * capitalize("foo bar").equals("Foo bar");
- * capitalize("2b or not 2b").equals("2b or not 2b")
- * capitalize("Foo bar").equals("Foo bar");
- * capitalize("").equals("");
- * </pre>
- *
- * @param s the string whose first character is to be uppercased
- * @return a string equivalent to <tt>s</tt> with its first character
- * converted to uppercase
- * @throws NullPointerException if <tt>s</tt> is null
- */
- public static String capitalize(String s) {
- if (s.length() == 0) {
- return s;
- }
- char first = s.charAt(0);
- char capitalized = Character.toUpperCase(first);
- return (first == capitalized)
- ? s
- : capitalized + s.substring(1);
- }
-
- /**
- * Examine a string to see if it starts with a given prefix (case
- * insensitive). Just like String.startsWith() except doesn't
- * respect case. Strings are compared in the same way as in
- * {@link String#equalsIgnoreCase}.
- *
- * @param str the string to examine
- * @param prefix the prefix to look for
- * @return a boolean indicating if str starts with prefix (case insensitive)
- */
- public static boolean startsWithIgnoreCase(String str, String prefix) {
- return str.regionMatches(true, 0, prefix, 0, prefix.length());
- }
-
- /**
- * Examine a string to see if it ends with a given suffix (case
- * insensitive). Just like String.endsWith() except doesn't respect
- * case. Strings are compared in the same way as in
- * {@link String#equalsIgnoreCase}.
- *
- * @param str the string to examine
- * @param suffix the suffix to look for
- * @return a boolean indicating if str ends with suffix (case insensitive)
- */
- public static boolean endsWithIgnoreCase(String str, String suffix) {
- int len = suffix.length();
- return str.regionMatches(true, str.length() - len, suffix, 0, len);
- }
-
- /**
- * @param c one codePoint
- * @return the number of bytes needed to encode this codePoint in UTF-8
- */
- private static int bytesUtf8(int c) {
- if (c < 0x80) {
- return 1;
- } else if (c < 0x00800) {
- return 2;
- } else if (c < 0x10000) {
- return 3;
- } else if (c < 0x200000) {
- return 4;
-
- // RFC 3629 forbids the use of UTF-8 for codePoint greater than 0x10FFFF,
- // so if the caller respects this RFC, this should not happen
- } else if (c < 0x4000000) {
- return 5;
- } else {
- return 6;
- }
- }
-
- /**
- * @param str a string
- * @return the number of bytes required to represent this string in UTF-8
- */
- public static int bytesStorage(String str) {
- // offsetByCodePoint has a bug if its argument is the result of a
- // call to substring. To avoid this, we create a new String
- // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6242664
- String s = new String(str);
-
- int len = 0;
- for (int i = 0; i < s.length(); i = s.offsetByCodePoints(i, 1)) {
- len += bytesUtf8(s.codePointAt(i));
- }
- return len;
- }
-
- /**
- * @param str a string
- * @param maxbytes
- * @return the beginning of the string, so that it uses less than
- * maxbytes bytes in UTF-8
- * @throws IndexOutOfBoundsException if maxbytes is negative
- */
- public static String truncateStringForUtf8Storage(String str, int maxbytes) {
- if (maxbytes < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- // offsetByCodePoint has a bug if its argument is the result of a
- // call to substring. To avoid this, we create a new String
- // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6242664
- // TODO(cquinn): should be fixed as of 1.5.0_01
- String s = new String(str);
-
- int codepoints = 0;
- int bytesUsed = 0;
- for (codepoints = 0; codepoints < s.length();
- codepoints = s.offsetByCodePoints(codepoints, 1)) {
- int glyphBytes = StringUtil.bytesUtf8(s.codePointAt(codepoints));
- if (bytesUsed + glyphBytes > maxbytes) {
- break;
- }
- bytesUsed += glyphBytes;
- }
- return s.substring(0, codepoints);
- }
-
- /**
- * If the given string is of length {@code maxLength} or less, then it is
- * returned as is.
- * If the string is longer than {@code maxLength}, the returned string is
- * truncated before the last space character on or before
- * {@code source.charAt(maxLength)}. If the string has no spaces, the
- * returned string is truncated to {@code maxLength}.
- *
- * @param source the string to truncate if necessary
- * @param maxLength
- * @return the original string if its length is less than or equal to
- * maxLength, otherwise a truncated string as mentioned above
- */
- public static String truncateIfNecessary(String source, int maxLength) {
- if (source.length() <= maxLength) {
- return source;
- }
- String str = unicodePreservingSubstring(source, 0, maxLength);
-
- @SuppressWarnings("deprecation") // we'll make this go away before that does
- CharMatcher whitespaceMatcher = CharMatcher.LEGACY_WHITESPACE;
- String truncated = whitespaceMatcher.trimTrailingFrom(str);
-
- // We may have had multiple spaces at maxLength, which were stripped away
- if (truncated.length() < maxLength) {
- return truncated;
- }
- // We have a truncated string of length maxLength. If the next char was a
- // space, we truncated at a word boundary, so we can return immediately
- if (Character.isSpaceChar(source.charAt(maxLength))) {
- return truncated;
- }
- // We truncated in the middle of the word. Try to truncate before
- // the last space, if it exists. Otherwise, return the truncated string
- for (int i = truncated.length() - 1; i >= 0; --i) {
- if (Character.isSpaceChar(truncated.charAt(i))) {
- String substr = truncated.substring(0, i);
- return whitespaceMatcher.trimTrailingFrom(substr);
- }
- }
- return truncated;
- }
-
- /**
- * If this given string is of length {@code maxLength} or less, it will
- * be returned as-is.
- * Otherwise it will be trucated to {@code maxLength}, regardless of whether
- * there are any space characters in the String. If an ellipsis is requested
- * to be appended to the truncated String, the String will be truncated so
- * that the ellipsis will also fit within maxLength.
- * If no truncation was necessary, no ellipsis will be added.
- *
- * @param source the String to truncate if necessary
- * @param maxLength the maximum number of characters to keep
- * @param addEllipsis if true, and if the String had to be truncated,
- * add "..." to the end of the String before returning. Additionally,
- * the ellipsis will only be added if maxLength is greater than 3.
- * @return the original string if its length is less than or equal to
- * maxLength, otherwise a truncated string as mentioned above
- */
- public static String truncateAtMaxLength(String source, int maxLength,
- boolean addEllipsis) {
-
- if (source.length() <= maxLength) {
- return source;
- }
- if (addEllipsis && maxLength > 3) {
- return unicodePreservingSubstring(source, 0, maxLength - 3) + "...";
- }
- return unicodePreservingSubstring(source, 0, maxLength);
- }
-
- /**
- * Normalizes {@code index} such that it respects Unicode character
- * boundaries in {@code str}.
- *
- * <p>If {@code index} is the low surrogate of a unicode character,
- * the method returns {@code index - 1}. Otherwise, {@code index} is
- * returned.
- *
- * <p>In the case in which {@code index} falls in an invalid surrogate pair
- * (e.g. consecutive low surrogates, consecutive high surrogates), or if
- * if it is not a valid index into {@code str}, the original value of
- * {@code index} is returned.
- *
- * @param str the String
- * @param index the index to be normalized
- * @return a normalized index that does not split a Unicode character
- */
- public static int unicodePreservingIndex(String str, int index) {
- if (index > 0 && index < str.length()) {
- if (Character.isHighSurrogate(str.charAt(index - 1)) &&
- Character.isLowSurrogate(str.charAt(index))) {
- return index - 1;
- }
- }
- return index;
- }
-
- /**
- * Returns a substring of {@code str} that respects Unicode character
- * boundaries.
- *
- * <p>The string will never be split between a [high, low] surrogate pair,
- * as defined by {@link Character#isHighSurrogate} and
- * {@link Character#isLowSurrogate}.
- *
- * <p>If {@code begin} or {@code end} are the low surrogate of a unicode
- * character, it will be offset by -1.
- *
- * <p>This behavior guarantees that
- * {@code str.equals(StringUtil.unicodePreservingSubstring(str, 0, n) +
- * StringUtil.unicodePreservingSubstring(str, n, str.length())) } is
- * true for all {@code n}.
- * </pre>
- *
- * <p>This means that unlike {@link String#substring(int, int)}, the length of
- * the returned substring may not necessarily be equivalent to
- * {@code end - begin}.
- *
- * @param str the original String
- * @param begin the beginning index, inclusive
- * @param end the ending index, exclusive
- * @return the specified substring, possibly adjusted in order to not
- * split unicode surrogate pairs
- * @throws IndexOutOfBoundsException if the {@code begin} is negative,
- * or {@code end} is larger than the length of {@code str}, or
- * {@code begin} is larger than {@code end}
- */
- public static String unicodePreservingSubstring(
- String str, int begin, int end) {
- return str.substring(unicodePreservingIndex(str, begin),
- unicodePreservingIndex(str, end));
- }
-
- /**
- * Equivalent to:
- *
- * <pre>
- * {@link #unicodePreservingSubstring(String, int, int)}(
- * str, begin, str.length())
- * </pre>
- */
- public static String unicodePreservingSubstring(String str, int begin) {
- return unicodePreservingSubstring(str, begin, str.length());
- }
-
- /**
- * True iff the given character needs to be escaped in a javascript string
- * literal.
- * <p>
- * We need to escape the following characters in javascript string literals.
- * <dl>
- * <dt> \ <dd> the escape character
- * <dt> ', " <dd> string delimiters.
- * TODO(msamuel): what about backticks (`) which are
- * non-standard but recognized as attribute delimiters.
- * <dt> &, <, >, = <dd> so that a string literal can be embedded in XHTML
- * without further escaping.
- * </dl>
- * TODO(msamuel): If we're being paranoid, should we escape + to avoid UTF-7
- * attacks?
- * <p>
- * Unicode format control characters (category Cf) must be escaped since they
- * are removed by javascript parser in a pre-lex pass.
- * <br>According to EcmaScript 262 Section 7.1:
- * <blockquote>
- * The format control characters can occur anywhere in the source text of
- * an ECMAScript program. These characters are removed from the source
- * text before applying the lexical grammar.
- * </blockquote>
- * <p>
- * Additionally, line terminators are not allowed to appear inside strings
- * and Section 7.3 says
- * <blockquote>
- * The following characters are considered to be line terminators:<pre>
- * Code Point Value Name Formal Name
- * \u000A Line Feed [LF]
- * \u000D Carriage Return [CR]
- * \u2028 Line separator [LS]
- * \u2029 Paragraph separator [PS]
- * </pre></blockquote>
- *
- * @param codepoint a char instead of an int since the javascript language
- * does not support extended unicode.
- */
- static boolean mustEscapeCharInJsString(int codepoint) {
- return JS_ESCAPE_CHARS.contains(codepoint);
- }
-
- /**
- * True iff the given character needs to be escaped in a JSON string literal.
- * <p>
- * We need to escape the following characters in JSON string literals.
- * <dl>
- * <dt> \ <dd> the escape character
- * <dt> " <dd> string delimiter
- * <dt> 0x00 - 0x1F <dd> control characters
- * </dl>
- * <p>
- * See EcmaScript 262 Section 15.12.1 for the full JSON grammar.
- */
- static boolean mustEscapeCharInJsonString(int codepoint) {
- return JSON_ESCAPE_CHARS.contains(codepoint);
- }
-
- /**
- * Builds a small set of code points.
- * {@code com.google.common.base} cannot depend on ICU4J, thus avoiding ICU's
- * {@code UnicodeSet}.
- * For all other purposes, please use {@code com.ibm.icu.text.UnicodeSet}.
- */
- private static class UnicodeSetBuilder {
- Set<Integer> codePointSet = new HashSet<Integer>();
-
- UnicodeSetBuilder addCodePoint(int c) {
- codePointSet.add(c);
- return this;
- }
-
- UnicodeSetBuilder addRange(int from, int to) {
- for (int i = from; i <= to; i++) {
- codePointSet.add(i);
- }
- return this;
- }
-
- Set<Integer> create() {
- return codePointSet;
- }
- }
-
- private static final Set<Integer> JS_ESCAPE_CHARS = new UnicodeSetBuilder()
- // All characters in the class of format characters, [:Cf:].
- // Source: http://unicode.org/cldr/utility/list-unicodeset.jsp.
- .addCodePoint(0xAD)
- .addRange(0x600, 0x603)
- .addCodePoint(0x6DD)
- .addCodePoint(0x070F)
- .addRange(0x17B4, 0x17B5)
- .addRange(0x200B, 0x200F)
- .addRange(0x202A, 0x202E)
- .addRange(0x2060, 0x2064)
- .addRange(0x206A, 0x206F)
- .addCodePoint(0xFEFF)
- .addRange(0xFFF9, 0xFFFB)
- .addRange(0x0001D173, 0x0001D17A)
- .addCodePoint(0x000E0001)
- .addRange(0x000E0020, 0x000E007F)
- // Plus characters mentioned in the docs of mustEscapeCharInJsString().
- .addCodePoint(0x0000)
- .addCodePoint(0x000A)
- .addCodePoint(0x000D)
- .addRange(0x2028, 0x2029)
- .addCodePoint(0x0085)
- .addCodePoint(Character.codePointAt("'", 0))
- .addCodePoint(Character.codePointAt("\"", 0))
- .addCodePoint(Character.codePointAt("&", 0))
- .addCodePoint(Character.codePointAt("<", 0))
- .addCodePoint(Character.codePointAt(">", 0))
- .addCodePoint(Character.codePointAt("=", 0))
- .addCodePoint(Character.codePointAt("\\", 0))
- .create();
-
- private static final Set<Integer> JSON_ESCAPE_CHARS = new UnicodeSetBuilder()
- .addCodePoint(Character.codePointAt("\"", 0))
- .addCodePoint(Character.codePointAt("\\", 0))
- .addRange(0x0000, 0x001F)
- .create();
-
- /**
- * <b>To be deprecated:</b> use {@link CharEscapers#xmlEscaper()} instead.
- */
- public static String xmlEscape(String s) {
- return CharEscapers.xmlEscaper().escape(s);
- }
-
- /**
- * <b>To be deprecated:</b> use {@link CharEscapers#asciiHtmlEscaper()} instead.
- */
- public static String htmlEscape(String s) {
- return CharEscapers.asciiHtmlEscaper().escape(s);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/Strings.java b/src/com/android/mail/lib/base/Strings.java
deleted file mode 100644
index 2fd6a97..0000000
--- a/src/com/android/mail/lib/base/Strings.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2010 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.mail.lib.base;
-
-import static com.android.mail.lib.base.Preconditions.checkArgument;
-import static com.android.mail.lib.base.Preconditions.checkNotNull;
-
-import java.util.Formatter;
-
-/**
- * Static utility methods pertaining to {@code String} or {@code CharSequence}
- * instances.
- *
- * @author Kevin Bourrillion
- * @since 3
- */
-public final class Strings {
- private Strings() {}
-
- /**
- * Returns the given string if it is non-null; the empty string otherwise.
- *
- * @param string the string to test and possibly return
- * @return {@code string} itself if it is non-null; {@code ""} if it is null
- */
- public static String nullToEmpty(String string) {
- return (string == null) ? "" : string;
- }
-
- /**
- * Returns the given string if it is nonempty; {@code null} otherwise.
- *
- * @param string the string to test and possibly return
- * @return {@code string} itself if it is nonempty; {@code null} if it is
- * empty or null
- */
- public static String emptyToNull(String string) {
- return isNullOrEmpty(string) ? null : string;
- }
-
- /**
- * Returns {@code true} if the given string is null or is the empty string.
- *
- * <p>Consider normalizing your string references with {@link #nullToEmpty}.
- * If you do, you can use {@link String#isEmpty()} instead of this
- * method, and you won't need special null-safe forms of methods like {@link
- * String#toUpperCase} either. Or, if you'd like to normalize "in the other
- * direction," converting empty strings to {@code null}, you can use {@link
- * #emptyToNull}.
- *
- * @param string a string reference to check
- * @return {@code true} if the string is null or is the empty string
- */
- public static boolean isNullOrEmpty(String string) {
- return string == null || string.length() == 0; // string.isEmpty() in Java 6
- }
-
- /**
- * Returns a string, of length at least {@code minLength}, consisting of
- * {@code string} prepended with as many copies of {@code padChar} as are
- * necessary to reach that length. For example,
- *
- * <ul>
- * <li>{@code padStart("7", 3, '0')} returns {@code "007"}
- * <li>{@code padStart("2010", 3, '0')} returns {@code "2010"}
- * </ul>
- *
- * <p>See {@link Formatter} for a richer set of formatting capabilities.
- *
- * @param string the string which should appear at the end of the result
- * @param minLength the minimum length the resulting string must have. Can be
- * zero or negative, in which case the input string is always returned.
- * @param padChar the character to insert at the beginning of the result until
- * the minimum length is reached
- * @return the padded string
- */
- public static String padStart(String string, int minLength, char padChar) {
- checkNotNull(string); // eager for GWT.
- if (string.length() >= minLength) {
- return string;
- }
- StringBuilder sb = new StringBuilder(minLength);
- for (int i = string.length(); i < minLength; i++) {
- sb.append(padChar);
- }
- sb.append(string);
- return sb.toString();
- }
-
- /**
- * Returns a string, of length at least {@code minLength}, consisting of
- * {@code string} appended with as many copies of {@code padChar} as are
- * necessary to reach that length. For example,
- *
- * <ul>
- * <li>{@code padEnd("4.", 5, '0')} returns {@code "4.000"}
- * <li>{@code padEnd("2010", 3, '!')} returns {@code "2010"}
- * </ul>
- *
- * <p>See {@link Formatter} for a richer set of formatting capabilities.
- *
- * @param string the string which should appear at the beginning of the result
- * @param minLength the minimum length the resulting string must have. Can be
- * zero or negative, in which case the input string is always returned.
- * @param padChar the character to append to the end of the result until the
- * minimum length is reached
- * @return the padded string
- */
- public static String padEnd(String string, int minLength, char padChar) {
- checkNotNull(string); // eager for GWT.
- if (string.length() >= minLength) {
- return string;
- }
- StringBuilder sb = new StringBuilder(minLength);
- sb.append(string);
- for (int i = string.length(); i < minLength; i++) {
- sb.append(padChar);
- }
- return sb.toString();
- }
-
- /**
- * Returns a string consisting of a specific number of concatenated copies of
- * an input string. For example, {@code repeat("hey", 3)} returns the string
- * {@code "heyheyhey"}.
- *
- * @param string any non-null string
- * @param count the number of times to repeat it; a nonnegative integer
- * @return a string containing {@code string} repeated {@code count} times
- * (the empty string if {@code count} is zero)
- * @throws IllegalArgumentException if {@code count} is negative
- */
- public static String repeat(String string, int count) {
- checkNotNull(string); // eager for GWT.
- checkArgument(count >= 0, "invalid count: %s", count);
-
- // If this multiplication overflows, a NegativeArraySizeException or
- // OutOfMemoryError is not far behind
- StringBuilder builder = new StringBuilder(string.length() * count);
- for (int i = 0; i < count; i++) {
- builder.append(string);
- }
- return builder.toString();
- }
-}
diff --git a/src/com/android/mail/lib/base/UnicodeEscaper.java b/src/com/android/mail/lib/base/UnicodeEscaper.java
deleted file mode 100644
index 95eb06e..0000000
--- a/src/com/android/mail/lib/base/UnicodeEscaper.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * Copyright (C) 2008 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.mail.lib.base;
-
-import static com.android.mail.lib.base.Preconditions.checkNotNull;
-import static com.android.mail.lib.base.Preconditions.checkPositionIndexes;
-
-import java.io.IOException;
-
-/**
- * An {@link Escaper} that converts literal text into a format safe for
- * inclusion in a particular context (such as an XML document). Typically (but
- * not always), the inverse process of "unescaping" the text is performed
- * automatically by the relevant parser.
- *
- * <p>For example, an XML escaper would convert the literal string {@code
- * "Foo<Bar>"} into {@code "Foo<Bar>"} to prevent {@code "<Bar>"} from
- * being confused with an XML tag. When the resulting XML document is parsed,
- * the parser API will return this text as the original literal string {@code
- * "Foo<Bar>"}.
- *
- * <p><b>Note:</b> This class is similar to {@link CharEscaper} but with one
- * very important difference. A CharEscaper can only process Java
- * <a href="http://en.wikipedia.org/wiki/UTF-16">UTF16</a> characters in
- * isolation and may not cope when it encounters surrogate pairs. This class
- * facilitates the correct escaping of all Unicode characters.
- *
- * <p>As there are important reasons, including potential security issues, to
- * handle Unicode correctly if you are considering implementing a new escaper
- * you should favor using UnicodeEscaper wherever possible.
- *
- * <p>A {@code UnicodeEscaper} instance is required to be stateless, and safe
- * when used concurrently by multiple threads.
- *
- * <p>Several popular escapers are defined as constants in the class {@link
- * CharEscapers}. To create your own escapers extend this class and implement
- * the {@link #escape(int)} method.
- *
- * @author dbeaumont@google.com (David Beaumont)
- */
-public abstract class UnicodeEscaper extends Escaper {
- /** The amount of padding (chars) to use when growing the escape buffer. */
- private static final int DEST_PAD = 32;
-
- /**
- * Returns the escaped form of the given Unicode code point, or {@code null}
- * if this code point does not need to be escaped. When called as part of an
- * escaping operation, the given code point is guaranteed to be in the range
- * {@code 0 <= cp <= Character#MAX_CODE_POINT}.
- *
- * <p>If an empty array is returned, this effectively strips the input
- * character from the resulting text.
- *
- * <p>If the character does not need to be escaped, this method should return
- * {@code null}, rather than an array containing the character representation
- * of the code point. This enables the escaping algorithm to perform more
- * efficiently.
- *
- * <p>If the implementation of this method cannot correctly handle a
- * particular code point then it should either throw an appropriate runtime
- * exception or return a suitable replacement character. It must never
- * silently discard invalid input as this may constitute a security risk.
- *
- * @param cp the Unicode code point to escape if necessary
- * @return the replacement characters, or {@code null} if no escaping was
- * needed
- */
- protected abstract char[] escape(int cp);
-
- /**
- * Scans a sub-sequence of characters from a given {@link CharSequence},
- * returning the index of the next character that requires escaping.
- *
- * <p><b>Note:</b> When implementing an escaper, it is a good idea to override
- * this method for efficiency. The base class implementation determines
- * successive Unicode code points and invokes {@link #escape(int)} for each of
- * them. If the semantics of your escaper are such that code points in the
- * supplementary range are either all escaped or all unescaped, this method
- * can be implemented more efficiently using {@link CharSequence#charAt(int)}.
- *
- * <p>Note however that if your escaper does not escape characters in the
- * supplementary range, you should either continue to validate the correctness
- * of any surrogate characters encountered or provide a clear warning to users
- * that your escaper does not validate its input.
- *
- * <p>See {@link PercentEscaper} for an example.
- *
- * @param csq a sequence of characters
- * @param start the index of the first character to be scanned
- * @param end the index immediately after the last character to be scanned
- * @throws IllegalArgumentException if the scanned sub-sequence of {@code csq}
- * contains invalid surrogate pairs
- */
- protected int nextEscapeIndex(CharSequence csq, int start, int end) {
- int index = start;
- while (index < end) {
- int cp = codePointAt(csq, index, end);
- if (cp < 0 || escape(cp) != null) {
- break;
- }
- index += Character.isSupplementaryCodePoint(cp) ? 2 : 1;
- }
- return index;
- }
-
- /**
- * Returns the escaped form of a given literal string.
- *
- * <p>If you are escaping input in arbitrary successive chunks, then it is not
- * generally safe to use this method. If an input string ends with an
- * unmatched high surrogate character, then this method will throw
- * {@link IllegalArgumentException}. You should either ensure your input is
- * valid <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a> before
- * calling this method or use an escaped {@link Appendable} (as returned by
- * {@link #escape(Appendable)}) which can cope with arbitrarily split input.
- *
- * <p><b>Note:</b> When implementing an escaper it is a good idea to override
- * this method for efficiency by inlining the implementation of
- * {@link #nextEscapeIndex(CharSequence, int, int)} directly. Doing this for
- * {@link PercentEscaper} more than doubled the performance for unescaped
- * strings (as measured by {@link CharEscapersBenchmark}).
- *
- * @param string the literal string to be escaped
- * @return the escaped form of {@code string}
- * @throws NullPointerException if {@code string} is null
- * @throws IllegalArgumentException if invalid surrogate characters are
- * encountered
- */
- @Override
- public String escape(String string) {
- checkNotNull(string);
- int end = string.length();
- int index = nextEscapeIndex(string, 0, end);
- return index == end ? string : escapeSlow(string, index);
- }
-
- /**
- * Returns the escaped form of a given literal string, starting at the given
- * index. This method is called by the {@link #escape(String)} method when it
- * discovers that escaping is required. It is protected to allow subclasses
- * to override the fastpath escaping function to inline their escaping test.
- * See {@link CharEscaperBuilder} for an example usage.
- *
- * <p>This method is not reentrant and may only be invoked by the top level
- * {@link #escape(String)} method.
- *
- * @param s the literal string to be escaped
- * @param index the index to start escaping from
- * @return the escaped form of {@code string}
- * @throws NullPointerException if {@code string} is null
- * @throws IllegalArgumentException if invalid surrogate characters are
- * encountered
- */
- protected final String escapeSlow(String s, int index) {
- int end = s.length();
-
- // Get a destination buffer and setup some loop variables.
- char[] dest = Platform.charBufferFromThreadLocal();
- int destIndex = 0;
- int unescapedChunkStart = 0;
-
- while (index < end) {
- int cp = codePointAt(s, index, end);
- if (cp < 0) {
- throw new IllegalArgumentException(
- "Trailing high surrogate at end of input");
- }
- // It is possible for this to return null because nextEscapeIndex() may
- // (for performance reasons) yield some false positives but it must never
- // give false negatives.
- char[] escaped = escape(cp);
- int nextIndex = index + (Character.isSupplementaryCodePoint(cp) ? 2 : 1);
- if (escaped != null) {
- int charsSkipped = index - unescapedChunkStart;
-
- // This is the size needed to add the replacement, not the full
- // size needed by the string. We only regrow when we absolutely must.
- int sizeNeeded = destIndex + charsSkipped + escaped.length;
- if (dest.length < sizeNeeded) {
- int destLength = sizeNeeded + (end - index) + DEST_PAD;
- dest = growBuffer(dest, destIndex, destLength);
- }
- // If we have skipped any characters, we need to copy them now.
- if (charsSkipped > 0) {
- s.getChars(unescapedChunkStart, index, dest, destIndex);
- destIndex += charsSkipped;
- }
- if (escaped.length > 0) {
- System.arraycopy(escaped, 0, dest, destIndex, escaped.length);
- destIndex += escaped.length;
- }
- // If we dealt with an escaped character, reset the unescaped range.
- unescapedChunkStart = nextIndex;
- }
- index = nextEscapeIndex(s, nextIndex, end);
- }
-
- // Process trailing unescaped characters - no need to account for escaped
- // length or padding the allocation.
- int charsSkipped = end - unescapedChunkStart;
- if (charsSkipped > 0) {
- int endIndex = destIndex + charsSkipped;
- if (dest.length < endIndex) {
- dest = growBuffer(dest, destIndex, endIndex);
- }
- s.getChars(unescapedChunkStart, end, dest, destIndex);
- destIndex = endIndex;
- }
- return new String(dest, 0, destIndex);
- }
-
- /**
- * Returns an {@code Appendable} instance which automatically escapes all
- * text appended to it before passing the resulting text to an underlying
- * {@code Appendable}.
- *
- * <p>Unlike {@link #escape(String)} it is permitted to append arbitrarily
- * split input to this Appendable, including input that is split over a
- * surrogate pair. In this case the pending high surrogate character will not
- * be processed until the corresponding low surrogate is appended. This means
- * that a trailing high surrogate character at the end of the input cannot be
- * detected and will be silently ignored. This is unavoidable since the
- * Appendable interface has no {@code close()} method, and it is impossible to
- * determine when the last characters have been appended.
- *
- * <p>The methods of the returned object will propagate any exceptions thrown
- * by the underlying {@code Appendable}.
- *
- * <p>For well formed <a href="http://en.wikipedia.org/wiki/UTF-16">UTF-16</a>
- * the escaping behavior is identical to that of {@link #escape(String)} and
- * the following code is equivalent to (but much slower than)
- * {@code escaper.escape(string)}: <pre>{@code
- *
- * StringBuilder sb = new StringBuilder();
- * escaper.escape(sb).append(string);
- * return sb.toString();}</pre>
- *
- * @param out the underlying {@code Appendable} to append escaped output to
- * @return an {@code Appendable} which passes text to {@code out} after
- * escaping it
- * @throws NullPointerException if {@code out} is null
- * @throws IllegalArgumentException if invalid surrogate characters are
- * encountered
- *
- * TODO(dbeaumont): Maybe return a Writer here so we have a close() method
- */
- @Override
- public Appendable escape(final Appendable out) {
- checkNotNull(out);
-
- return new Appendable() {
- char pendingHighSurrogate = 0;
-
- @Override
- public Appendable append(CharSequence csq) throws IOException {
- return append(csq, 0, csq.length());
- }
-
- @Override
- public Appendable append(CharSequence csq, int start, int end)
- throws IOException {
- checkNotNull(csq);
- checkPositionIndexes(start, end, csq.length());
-
- // If there is a pending high surrogate, handle it and start at the
- // next character.
- if (pendingHighSurrogate != 0 && start < end) {
- completeSurrogatePair(csq.charAt(start++));
- }
-
- if (start < end) {
- // If the string ends with a high surrogate, store it for the next
- // append, and skip that character from the current escaping.
- char last = csq.charAt(end - 1);
- if (Character.isHighSurrogate(last)) {
- pendingHighSurrogate = last;
- end--;
- }
-
- // Escape the subsequence from start to end, which cannot legally
- // contain an unpaired surrogate
- out.append(escape(csq.subSequence(start, end).toString()));
- }
- return this;
- }
-
- @Override
- public Appendable append(char c) throws IOException {
- if (pendingHighSurrogate != 0) {
- completeSurrogatePair(c);
- } else if (Character.isHighSurrogate(c)) {
- pendingHighSurrogate = c;
- } else {
- if (Character.isLowSurrogate(c)) {
- throw new IllegalArgumentException(
- "Unexpected low surrogate character '" + c +
- "' with value " + (int) c);
- }
- // This is a normal (non surrogate) char.
- char[] escaped = escape(c);
- if (escaped != null) {
- outputChars(escaped);
- } else {
- out.append(c);
- }
- }
- return this;
- }
-
- /**
- * Our last append operation ended halfway through a surrogate pair so we
- * complete the surrogate pair using {@code c}, which must be a low
- * surrogate.
- */
- private void completeSurrogatePair(char c) throws IOException {
- if (!Character.isLowSurrogate(c)) {
- throw new IllegalArgumentException(
- "Expected low surrogate character but got '" + c +
- "' with value " + (int) c);
- }
- char[] escaped = escape(
- Character.toCodePoint(pendingHighSurrogate, c));
- if (escaped != null) {
- outputChars(escaped);
- } else {
- out.append(pendingHighSurrogate);
- out.append(c);
- }
- pendingHighSurrogate = 0;
- }
-
- /**
- * Output some characters to the underlying appendable.
- */
- private void outputChars(char[] chars) throws IOException {
- for (int n = 0; n < chars.length; n++) {
- out.append(chars[n]);
- }
- }
- };
- }
-
- /**
- * Returns the Unicode code point of the character at the given index.
- *
- * <p>Unlike {@link Character#codePointAt(CharSequence, int)} or
- * {@link String#codePointAt(int)} this method will never fail silently when
- * encountering an invalid surrogate pair.
- *
- * <p>The behaviour of this method is as follows:
- * <ol>
- * <li>If {@code index >= end}, {@link IndexOutOfBoundsException} is thrown.
- * <li><b>If the character at the specified index is not a surrogate, it is
- * returned.</b>
- * <li>If the first character was a high surrogate value, then an attempt is
- * made to read the next character.
- * <ol>
- * <li><b>If the end of the sequence was reached, the negated value of
- * the trailing high surrogate is returned.</b>
- * <li><b>If the next character was a valid low surrogate, the code point
- * value of the high/low surrogate pair is returned.</b>
- * <li>If the next character was not a low surrogate value, then
- * {@link IllegalArgumentException} is thrown.
- * </ol>
- * <li>If the first character was a low surrogate value,
- * {@link IllegalArgumentException} is thrown.
- * </ol>
- *
- * @param seq the sequence of characters from which to decode the code point
- * @param index the index of the first character to decode
- * @param end the index beyond the last valid character to decode
- * @return the Unicode code point for the given index or the negated value of
- * the trailing high surrogate character at the end of the sequence
- */
- protected static final int codePointAt(CharSequence seq, int index, int end) {
- if (index < end) {
- char c1 = seq.charAt(index++);
- if (c1 < Character.MIN_HIGH_SURROGATE ||
- c1 > Character.MAX_LOW_SURROGATE) {
- // Fast path (first test is probably all we need to do)
- return c1;
- } else if (c1 <= Character.MAX_HIGH_SURROGATE) {
- // If the high surrogate was the last character, return its inverse
- if (index == end) {
- return -c1;
- }
- // Otherwise look for the low surrogate following it
- char c2 = seq.charAt(index);
- if (Character.isLowSurrogate(c2)) {
- return Character.toCodePoint(c1, c2);
- }
- throw new IllegalArgumentException(
- "Expected low surrogate but got char '" + c2 +
- "' with value " + (int) c2 + " at index " + index);
- } else {
- throw new IllegalArgumentException(
- "Unexpected low surrogate character '" + c1 +
- "' with value " + (int) c1 + " at index " + (index - 1));
- }
- }
- throw new IndexOutOfBoundsException("Index exceeds specified range");
- }
-
- /**
- * Helper method to grow the character buffer as needed, this only happens
- * once in a while so it's ok if it's in a method call. If the index passed
- * in is 0 then no copying will be done.
- */
- private static final char[] growBuffer(char[] dest, int index, int size) {
- char[] copy = new char[size];
- if (index > 0) {
- System.arraycopy(dest, 0, copy, 0, index);
- }
- return copy;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/base/X.java b/src/com/android/mail/lib/base/X.java
deleted file mode 100644
index 62bf032..0000000
--- a/src/com/android/mail/lib/base/X.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2000 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.mail.lib.base;
-
-/**
- * A utility class that contains some very widely used functionality.
- * This class is named "X" just to get a short name that can be typed
- * everywhere without cluttering up the code. For example, it
- * seems a lot less verbose to say: "X.assertTrue(empty())" instead of
- * "Assert.assertTrue(empty())".
- *
- * <p>Consider using {@link Preconditions} instead though.
- *
- * <p>If your application is using JDK 1.4, feel free to use the built-in
- * assert() methods instead. <b>NOTE:</b> Except remember that JDK assertions
- * are not normally enabled unless you pass the -ea flag to the jvm.
- */
-public final class X {
-
- /**
- * This class should not be instantiated. It provides static methods
- * only.
- */
- private X() {}
-
- /**
- * Raise a runtime exception if the supplied argument is false (note: if you
- * are checking a precondition, please use {@link Preconditions} instead).
- */
- public static void assertTrue(boolean b) {
- if (!b)
- throw new RuntimeException("Assertion failed");
- }
-
- /**
- * Raise a runtime exception if the supplied argument is false and print
- * out the error message (note: if you are checking a precondition, please use
- * {@link Preconditions} instead).
- */
- public static void assertTrue(boolean b, String msg) {
- if (!b)
- throw new RuntimeException("Assertion failed: " + msg);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/html/parser/HTML.java b/src/com/android/mail/lib/html/parser/HTML.java
deleted file mode 100644
index 0ed05f7..0000000
--- a/src/com/android/mail/lib/html/parser/HTML.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/**
- * Copyright (c) 2004, 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.mail.lib.html.parser;
-
-import com.android.mail.lib.base.Preconditions;
-
-import java.util.Set;
-
-/**
- * HTML class defines Element and Attribute classes.
- *
- * @author jlim@google.com (Jing Yee Lim)
- */
-public final class HTML {
-
- /**
- * Html element
- */
- public static final class Element {
-
- // TODO(ptucker) other candidate types are list and form elements. Better for this to be
- // enumerated type.
- /** Types */
- public static final int NO_TYPE = 0;
- public static final int TABLE_TYPE = 1;
-
- /**
- * INLINE - charater level elements and text strings
- * BLOCK - block-like elements; e.g., paragraphs and lists
- * NONE - everything else
- */
- public enum Flow {
- INLINE,
- BLOCK,
- NONE
- }
-
- private final String name;
- private final int type;
- private final boolean empty;
- private final boolean optionalEndTag;
- private final boolean breaksFlow;
- private final Flow flow;
-
- /**
- * Construct an Element.
- *
- * NOTE: Even though breaksFlow and flow are named similarly, they're not quite the same thing.
- * Flow refers to whether the element is inherently character or block level. Breaks flow
- * refers to whether it forces a line break.
- *
- * @throws IllegalArgumentException if name or flow is null.
- */
- public Element(String name, int type, boolean empty,
- boolean optionalEndTag, boolean breaksFlow, Flow flow) {
- Preconditions.checkNotNull(name, "Element name can not be null");
- Preconditions.checkNotNull(flow, "Element flow can not be null");
- this.name = name;
- this.type = type;
- this.empty = empty;
- this.optionalEndTag = optionalEndTag;
- this.breaksFlow = breaksFlow;
- this.flow = flow;
- }
-
- /**
- * Construct an Element with inline=true.
- */
- public Element(String name, int type, boolean empty,
- boolean optionalEndTag, boolean breaksFlow) {
- this(name, type, empty, optionalEndTag, breaksFlow, Flow.NONE);
- }
-
- /** Name of the element, in lowercase, e.g. "a", "br" */
- public String getName() {
- return name;
- }
-
- /** Type, e.g. TABLE_TYPE */
- public int getType() {
- return type;
- }
-
- /** True if it's empty, has no inner elements or end tag */
- public boolean isEmpty() {
- return empty;
- }
-
- /** True if the end tag is optional */
- public boolean isEndTagOptional() {
- return optionalEndTag;
- }
-
- /**
- * True if it breaks the flow, and may force a new line before/after the
- * tag.
- */
- public boolean breaksFlow() {
- return breaksFlow;
- }
-
- /** Flow type. */
- public Flow getFlow() {
- return flow;
- }
-
- /**
- * @return just name, not proper HTML
- */
- @Override
- public String toString() {
- return name;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o instanceof HTML.Element) {
- HTML.Element that = (HTML.Element) o;
- return this.name.equals(that.name);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return this.name.hashCode();
- }
- }
-
- /**
- * Html attribute
- */
- public static final class Attribute {
- /** Value types */
- public static final int NO_TYPE = 0;
- public static final int URI_TYPE = 1;
- public static final int SCRIPT_TYPE = 2;
- public static final int ENUM_TYPE = 3;
- public static final int BOOLEAN_TYPE = 4;
-
- /** Name of the element, e.g. "HREF" */
- private final String name;
-
- /** Type of the attribute value, e.g. URI_TYPE */
- private final int type;
-
- /** The list of allowed values, or null if any value is allowed */
- private final Set<String> values;
-
- /**
- * Construct an Attribute
- * @throws IllegalArgumentException if name is null
- */
- public Attribute(String name, int type) {
- this(name, type, null);
- }
-
- /**
- * Construct an Attribute
- * @throws IllegalArgumentException if name is null
- * or if Attribute is of type ENUM_TYPE and the values are null
- */
- public Attribute(String name, int type, Set<String> values) {
- Preconditions.checkNotNull(name, "Attribute name can not be null");
- Preconditions.checkArgument((values == null) ^ (type == ENUM_TYPE),
- "Only ENUM_TYPE can have values != null");
- this.name = name;
- this.type = type;
- this.values = values;
- }
-
- /** Gets the name of the attribute, in lowercase */
- public String getName() {
- return name;
- }
-
- /** Gets the type, e.g. URI_TYPE */
- public int getType() {
- return type;
- }
-
- /**
- * When called on an attribute of ENUM_TYPE, returns a Set of Strings
- * containing the allowed attribute values. The return set is guaranteed to
- * only contain lower case Strings.
- *
- * @return a Set of Strings, in lower case, for the allowed attribute
- * values.
- * @throws IllegalStateException if attribute type is not ENUM_TYPE
- */
- public Set<String> getEnumValues() {
- Preconditions.checkState(type == ENUM_TYPE);
- return values;
- }
-
- /**
- * @return Element name (name only, not proper HTML).
- */
- @Override
- public String toString() {
- return name;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o instanceof HTML.Attribute) {
- HTML.Attribute that = (HTML.Attribute) o;
- return this.name.equals(that.name);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return this.name.hashCode();
- }
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/html/parser/HTML4.java b/src/com/android/mail/lib/html/parser/HTML4.java
deleted file mode 100644
index 9101fa2..0000000
--- a/src/com/android/mail/lib/html/parser/HTML4.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/**
- * Copyright (c) 2004, 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.mail.lib.html.parser;
-
-import com.google.common.collect.Maps;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * HTML4 contains HTML 4.0 definitions and specifications
- * See http://www.w3.org/TR/html401/
- * See http://www.w3.org/TR/html401/index/elements.html
- * See http://www.w3.org/TR/html401/index/attributes.html
- *
- * @author jlim@google.com (Jing Yee Lim)
- */
-public final class HTML4 {
-
- /** Map of all elements */
- private static final HashMap<String,HTML.Element> elements = Maps.newHashMap();
-
- /** Map of all attributes */
- private static final HashMap<String,HTML.Attribute> attributes = Maps.newHashMap();
-
- /** Default Whitelist */
- private static final HtmlWhitelist defaultWhitelist = new HtmlWhitelist() {
- /**
- * @see com.google.common.html.parser.HtmlWhitelist#lookupElement(String)
- */
- public HTML.Element lookupElement(String name) {
- return HTML4.lookupElement(name);
- }
-
- /**
- * @see com.google.common.html.parser.HtmlWhitelist#lookupAttribute(String)
- */
- public HTML.Attribute lookupAttribute(String name) {
- return HTML4.lookupAttribute(name);
- }
- };
-
- /** Gets the default Whitelist */
- public static HtmlWhitelist getWhitelist() {
- return HTML4.defaultWhitelist;
- }
-
- /** Looks for a HTML4 element */
- public static HTML.Element lookupElement(String name) {
- return elements.get(name.toLowerCase());
- }
-
- /** Looks for a HTML4 attribute */
- public static HTML.Attribute lookupAttribute(String name) {
- return attributes.get(name.toLowerCase());
- }
-
- /**
- * @return Unmodifiable Map of all valid HTML4 elements. Key is lowercase
- * element name.
- */
- public static Map<String, HTML.Element> getAllElements() {
- return Collections.unmodifiableMap(elements);
- }
-
- /**
- * @return Unmodifiable Map of all valid HTML4 attributes. Key is lowercase
- * attribute name.
- */
- public static Map<String, HTML.Attribute> getAllAttributes() {
- return Collections.unmodifiableMap(attributes);
- }
-
- /** Creates and adds a element to the map */
- private static HTML.Element addElement(String tag, String flags) {
- return addElement(tag, flags, HTML.Element.Flow.NONE);
- }
-
- /** Creates and adds a element to the map */
- private static HTML.Element addElement(String tag, String flags, HTML.Element.Flow flow) {
- return addElement(tag, flags, flow, HTML.Element.NO_TYPE);
- }
-
- /** Creates and adds a element to the map */
- private static HTML.Element addTableElement(String tag, String flags, HTML.Element.Flow flow) {
- return addElement(tag, flags, flow, HTML.Element.TABLE_TYPE);
- }
-
- /** Creates and adds a element to the map */
- private static HTML.Element addElement(String tag, String flags, HTML.Element.Flow flow,
- int type) {
- tag = tag.toLowerCase();
-
- boolean empty = false;
- boolean optionalEndTag = false;
- boolean breaksFlow = false;
- for (int i = 0; i < flags.length(); i++) {
- switch (flags.charAt(i)) {
- case 'E': empty = true; break;
- case 'O': optionalEndTag = true; break;
- case 'B': breaksFlow = true; break;
- default: throw new Error("Unknown element flag");
- }
- }
- HTML.Element element = new HTML.Element(tag, type, empty, optionalEndTag, breaksFlow, flow);
- elements.put(tag, element);
- return element;
- }
-
- /** Creates and add an attribute to the map */
- private static HTML.Attribute addAttribute(String attribute) {
- return addAttribute(attribute, HTML.Attribute.NO_TYPE);
- }
-
- private static HTML.Attribute addAttribute(String attribute, int type) {
- return addAttribute(attribute, type, null);
- }
-
- private static HTML.Attribute addAttribute(String attribute,
- int type,
- String[] values) {
- attribute = attribute.toLowerCase();
- Set<String> valueSet = null;
- if (values != null) {
- valueSet = new HashSet<String>();
- for (String x : values) {
- valueSet.add(x.toLowerCase());
- }
- valueSet = Collections.unmodifiableSet(valueSet);
- }
- HTML.Attribute attr = new HTML.Attribute(attribute, type, valueSet);
- attributes.put(attribute, attr);
- return attr;
- }
-
- /**
- * All HTML4 elements.
- *
- * Block vs inline flow:
- * http://www.w3.org/TR/REC-html40/sgml/dtd.html#block
- * http://www.w3.org/TR/REC-html40/sgml/dtd.html#inline
- * Some deprecated elements aren't listed there so we make an educated guess:
- * - CENTER is equivalent to DIV align=center, so we make it BLOCK.
- * - S, STRIKE and FONT are clearly inline like U, I, etc.
- * - MENU and DIR are like OL and UL, so we make them block.
- *
- * Optional end tag and empty:
- * http://www.w3.org/TR/REC-html40/index/elements.html
- */
- public static final HTML.Element
- A_ELEMENT = addElement("A", "", HTML.Element.Flow.INLINE),
- ABBR_ELEMENT = addElement("ABBR", "", HTML.Element.Flow.INLINE),
- ACRONYM_ELEMENT = addElement("ACRONYM", "", HTML.Element.Flow.INLINE),
- ADDRESS_ELEMENT = addElement("ADDRESS", "", HTML.Element.Flow.BLOCK),
- APPLET_ELEMENT = addElement("APPLET", ""),
- AREA_ELEMENT = addElement("AREA", "E"),
- B_ELEMENT = addElement("B", "", HTML.Element.Flow.INLINE),
- BASE_ELEMENT = addElement("BASE", "E"),
- BASEFONT_ELEMENT = addElement("BASEFONT", "E"),
- BDO_ELEMENT = addElement("BDO", "", HTML.Element.Flow.INLINE),
- BIG_ELEMENT = addElement("BIG", "", HTML.Element.Flow.INLINE),
- BLOCKQUOTE_ELEMENT = addElement("BLOCKQUOTE", "B", HTML.Element.Flow.BLOCK),
- BODY_ELEMENT = addElement("BODY", "O"),
- BR_ELEMENT = addElement("BR", "EB", HTML.Element.Flow.INLINE),
- BUTTON_ELEMENT = addElement("BUTTON", "", HTML.Element.Flow.INLINE),
- CAPTION_ELEMENT = addTableElement("CAPTION", "", HTML.Element.Flow.NONE),
- CENTER_ELEMENT = addElement("CENTER", "B", HTML.Element.Flow.BLOCK),
- CITE_ELEMENT = addElement("CITE", "", HTML.Element.Flow.INLINE),
- CODE_ELEMENT = addElement("CODE", "", HTML.Element.Flow.INLINE),
- COL_ELEMENT = addTableElement("COL", "E", HTML.Element.Flow.NONE),
- COLGROUP_ELEMENT = addTableElement("COLGROUP", "O", HTML.Element.Flow.NONE),
- DD_ELEMENT = addElement("DD", "OB"),
- DEL_ELEMENT = addElement("DEL", ""),
- DFN_ELEMENT = addElement("DFN", "", HTML.Element.Flow.INLINE),
- DIR_ELEMENT = addElement("DIR", "B", HTML.Element.Flow.BLOCK),
- DIV_ELEMENT = addElement("DIV", "B", HTML.Element.Flow.BLOCK),
- DL_ELEMENT = addElement("DL", "B", HTML.Element.Flow.BLOCK),
- DT_ELEMENT = addElement("DT", "OB"),
- EM_ELEMENT = addElement("EM", "", HTML.Element.Flow.INLINE),
- FIELDSET_ELEMENT = addElement("FIELDSET", "", HTML.Element.Flow.BLOCK),
- FONT_ELEMENT = addElement("FONT", "", HTML.Element.Flow.INLINE),
- FORM_ELEMENT = addElement("FORM", "B", HTML.Element.Flow.BLOCK),
- FRAME_ELEMENT = addElement("FRAME", "E"),
- FRAMESET_ELEMENT = addElement("FRAMESET", ""),
- H1_ELEMENT = addElement("H1", "B", HTML.Element.Flow.BLOCK),
- H2_ELEMENT = addElement("H2", "B", HTML.Element.Flow.BLOCK),
- H3_ELEMENT = addElement("H3", "B", HTML.Element.Flow.BLOCK),
- H4_ELEMENT = addElement("H4", "B", HTML.Element.Flow.BLOCK),
- H5_ELEMENT = addElement("H5", "B", HTML.Element.Flow.BLOCK),
- H6_ELEMENT = addElement("H6", "B", HTML.Element.Flow.BLOCK),
- HEAD_ELEMENT = addElement("HEAD", "OB"),
- HR_ELEMENT = addElement("HR", "EB", HTML.Element.Flow.BLOCK),
- HTML_ELEMENT = addElement("HTML", "OB"),
- I_ELEMENT = addElement("I", "", HTML.Element.Flow.INLINE),
- IFRAME_ELEMENT = addElement("IFRAME", ""),
- IMG_ELEMENT = addElement("IMG", "E", HTML.Element.Flow.INLINE),
- INPUT_ELEMENT = addElement("INPUT", "E", HTML.Element.Flow.INLINE),
- INS_ELEMENT = addElement("INS", ""),
- ISINDEX_ELEMENT = addElement("ISINDEX", "EB"),
- KBD_ELEMENT = addElement("KBD", "", HTML.Element.Flow.INLINE),
- LABEL_ELEMENT = addElement("LABEL", "", HTML.Element.Flow.INLINE),
- LEGEND_ELEMENT = addElement("LEGEND", ""),
- LI_ELEMENT = addElement("LI", "OB"),
- LINK_ELEMENT = addElement("LINK", "E"),
- MAP_ELEMENT = addElement("MAP", "", HTML.Element.Flow.INLINE),
- MENU_ELEMENT = addElement("MENU", "B", HTML.Element.Flow.BLOCK),
- META_ELEMENT = addElement("META", "E"),
- NOFRAMES_ELEMENT = addElement("NOFRAMES", "B"),
- NOSCRIPT_ELEMENT = addElement("NOSCRIPT", "", HTML.Element.Flow.BLOCK),
- OBJECT_ELEMENT = addElement("OBJECT", "", HTML.Element.Flow.INLINE),
- OL_ELEMENT = addElement("OL", "B", HTML.Element.Flow.BLOCK),
- OPTGROUP_ELEMENT = addElement("OPTGROUP", ""),
- OPTION_ELEMENT = addElement("OPTION", "O"),
- P_ELEMENT = addElement("P", "OB", HTML.Element.Flow.BLOCK),
- PARAM_ELEMENT = addElement("PARAM", "E"),
- PRE_ELEMENT = addElement("PRE", "B", HTML.Element.Flow.BLOCK),
- Q_ELEMENT = addElement("Q", "", HTML.Element.Flow.INLINE),
- S_ELEMENT = addElement("S", "", HTML.Element.Flow.INLINE),
- SAMP_ELEMENT = addElement("SAMP", "", HTML.Element.Flow.INLINE),
- SCRIPT_ELEMENT = addElement("SCRIPT", "", HTML.Element.Flow.INLINE),
- SELECT_ELEMENT = addElement("SELECT", "", HTML.Element.Flow.INLINE),
- SMALL_ELEMENT = addElement("SMALL", "", HTML.Element.Flow.INLINE),
- SPAN_ELEMENT = addElement("SPAN", "", HTML.Element.Flow.INLINE),
- STRIKE_ELEMENT = addElement("STRIKE", "", HTML.Element.Flow.INLINE),
- STRONG_ELEMENT = addElement("STRONG", "", HTML.Element.Flow.INLINE),
- STYLE_ELEMENT = addElement("STYLE", ""),
- SUB_ELEMENT = addElement("SUB", "", HTML.Element.Flow.INLINE),
- SUP_ELEMENT = addElement("SUP", "", HTML.Element.Flow.INLINE),
- TABLE_ELEMENT = addTableElement("TABLE", "B", HTML.Element.Flow.BLOCK),
- TBODY_ELEMENT = addTableElement("TBODY", "O", HTML.Element.Flow.NONE),
- TD_ELEMENT = addTableElement("TD", "OB", HTML.Element.Flow.NONE),
- TEXTAREA_ELEMENT = addElement("TEXTAREA", "", HTML.Element.Flow.INLINE),
- TFOOT_ELEMENT = addTableElement("TFOOT", "O", HTML.Element.Flow.NONE),
- TH_ELEMENT = addTableElement("TH", "OB", HTML.Element.Flow.NONE),
- THEAD_ELEMENT = addTableElement("THEAD", "O", HTML.Element.Flow.NONE),
- TITLE_ELEMENT = addElement("TITLE", "B"),
- TR_ELEMENT = addTableElement("TR", "OB", HTML.Element.Flow.NONE),
- TT_ELEMENT = addElement("TT", "", HTML.Element.Flow.INLINE),
- U_ELEMENT = addElement("U", "", HTML.Element.Flow.INLINE),
- UL_ELEMENT = addElement("UL", "B", HTML.Element.Flow.BLOCK),
- VAR_ELEMENT = addElement("VAR", "", HTML.Element.Flow.INLINE);
-
- /**
- * All the HTML4 attributes
- */
- public static final HTML.Attribute
- ABBR_ATTRIBUTE = addAttribute("ABBR"),
- ACCEPT_ATTRIBUTE = addAttribute("ACCEPT"),
- ACCEPT_CHARSET_ATTRIBUTE = addAttribute("ACCEPT-CHARSET"),
- ACCESSKEY_ATTRIBUTE = addAttribute("ACCESSKEY"),
- ACTION_ATTRIBUTE = addAttribute("ACTION", HTML.Attribute.URI_TYPE),
- ALIGN_ATTRIBUTE = addAttribute("ALIGN",
- HTML.Attribute.ENUM_TYPE,
- new String[] {"left", "center", "right", "justify",
- "char", "top", "bottom", "middle"}),
- ALINK_ATTRIBUTE = addAttribute("ALINK"),
- ALT_ATTRIBUTE = addAttribute("ALT"),
- ARCHIVE_ATTRIBUTE = addAttribute("ARCHIVE", HTML.Attribute.URI_TYPE),
- AXIS_ATTRIBUTE = addAttribute("AXIS"),
- BACKGROUND_ATTRIBUTE = addAttribute("BACKGROUND", HTML.Attribute.URI_TYPE),
- BGCOLOR_ATTRIBUTE = addAttribute("BGCOLOR"),
- BORDER_ATTRIBUTE = addAttribute("BORDER"),
- CELLPADDING_ATTRIBUTE = addAttribute("CELLPADDING"),
- CELLSPACING_ATTRIBUTE = addAttribute("CELLSPACING"),
- CHAR_ATTRIBUTE = addAttribute("CHAR"),
- CHAROFF_ATTRIBUTE = addAttribute("CHAROFF"),
- CHARSET_ATTRIBUTE = addAttribute("CHARSET"),
- CHECKED_ATTRIBUTE = addAttribute("CHECKED", HTML.Attribute.BOOLEAN_TYPE),
- CITE_ATTRIBUTE = addAttribute("CITE", HTML.Attribute.URI_TYPE),
- CLASS_ATTRIBUTE = addAttribute("CLASS"),
- CLASSID_ATTRIBUTE = addAttribute("CLASSID", HTML.Attribute.URI_TYPE),
- CLEAR_ATTRIBUTE = addAttribute("CLEAR",
- HTML.Attribute.ENUM_TYPE,
- new String[] {"left", "all", "right", "none"}),
- CODE_ATTRIBUTE = addAttribute("CODE"),
- CODEBASE_ATTRIBUTE = addAttribute("CODEBASE", HTML.Attribute.URI_TYPE),
- CODETYPE_ATTRIBUTE = addAttribute("CODETYPE"),
- COLOR_ATTRIBUTE = addAttribute("COLOR"),
- COLS_ATTRIBUTE = addAttribute("COLS"),
- COLSPAN_ATTRIBUTE = addAttribute("COLSPAN"),
- COMPACT_ATTRIBUTE = addAttribute("COMPACT", HTML.Attribute.BOOLEAN_TYPE),
- CONTENT_ATTRIBUTE = addAttribute("CONTENT"),
- COORDS_ATTRIBUTE = addAttribute("COORDS"),
- DATA_ATTRIBUTE = addAttribute("DATA", HTML.Attribute.URI_TYPE),
- DATETIME_ATTRIBUTE = addAttribute("DATETIME"),
- DECLARE_ATTRIBUTE = addAttribute("DECLARE", HTML.Attribute.BOOLEAN_TYPE),
- DEFER_ATTRIBUTE = addAttribute("DEFER", HTML.Attribute.BOOLEAN_TYPE),
- DIR_ATTRIBUTE = addAttribute("DIR",
- HTML.Attribute.ENUM_TYPE,
- new String[] {"ltr", "rtl"}),
- DISABLED_ATTRIBUTE = addAttribute("DISABLED", HTML.Attribute.BOOLEAN_TYPE),
- ENCTYPE_ATTRIBUTE = addAttribute("ENCTYPE"),
- FACE_ATTRIBUTE = addAttribute("FACE"),
- FOR_ATTRIBUTE = addAttribute("FOR"),
- FRAME_ATTRIBUTE = addAttribute("FRAME"),
- FRAMEBORDER_ATTRIBUTE = addAttribute("FRAMEBORDER",
- HTML.Attribute.ENUM_TYPE,
- new String[] {"1", "0"}),
- HEADERS_ATTRIBUTE = addAttribute("HEADERS"),
- HEIGHT_ATTRIBUTE = addAttribute("HEIGHT"),
- HREF_ATTRIBUTE = addAttribute("HREF", HTML.Attribute.URI_TYPE),
- HREFLANG_ATTRIBUTE = addAttribute("HREFLANG"),
- HSPACE_ATTRIBUTE = addAttribute("HSPACE"),
- HTTP_EQUIV_ATTRIBUTE = addAttribute("HTTP-EQUIV"),
- ID_ATTRIBUTE = addAttribute("ID"),
- ISMAP_ATTRIBUTE = addAttribute("ISMAP", HTML.Attribute.BOOLEAN_TYPE),
- LABEL_ATTRIBUTE = addAttribute("LABEL"),
- LANG_ATTRIBUTE = addAttribute("LANG"),
- LANGUAGE_ATTRIBUTE = addAttribute("LANGUAGE"),
- LINK_ATTRIBUTE = addAttribute("LINK"),
- LONGDESC_ATTRIBUTE = addAttribute("LONGDESC", HTML.Attribute.URI_TYPE),
- MARGINHEIGHT_ATTRIBUTE = addAttribute("MARGINHEIGHT"),
- MARGINWIDTH_ATTRIBUTE = addAttribute("MARGINWIDTH"),
- MAXLENGTH_ATTRIBUTE = addAttribute("MAXLENGTH"),
- MEDIA_ATTRIBUTE = addAttribute("MEDIA"),
- METHOD_ATTRIBUTE = addAttribute("METHOD",
- HTML.Attribute.ENUM_TYPE,
- new String[] {"get", "post"}),
- MULTIPLE_ATTRIBUTE = addAttribute("MULTIPLE", HTML.Attribute.BOOLEAN_TYPE),
- NAME_ATTRIBUTE = addAttribute("NAME"),
- NOHREF_ATTRIBUTE = addAttribute("NOHREF", HTML.Attribute.BOOLEAN_TYPE),
- NORESIZE_ATTRIBUTE = addAttribute("NORESIZE", HTML.Attribute.BOOLEAN_TYPE),
- NOSHADE_ATTRIBUTE = addAttribute("NOSHADE", HTML.Attribute.BOOLEAN_TYPE),
- NOWRAP_ATTRIBUTE = addAttribute("NOWRAP", HTML.Attribute.BOOLEAN_TYPE),
- OBJECT_ATTRIBUTE = addAttribute("OBJECT"),
- ONBLUR_ATTRIBUTE = addAttribute("ONBLUR", HTML.Attribute.SCRIPT_TYPE),
- ONCHANGE_ATTRIBUTE = addAttribute("ONCHANGE", HTML.Attribute.SCRIPT_TYPE),
- ONCLICK_ATTRIBUTE = addAttribute("ONCLICK", HTML.Attribute.SCRIPT_TYPE),
- ONDBLCLICK_ATTRIBUTE = addAttribute("ONDBLCLICK", HTML.Attribute.SCRIPT_TYPE),
- ONFOCUS_ATTRIBUTE = addAttribute("ONFOCUS", HTML.Attribute.SCRIPT_TYPE),
- ONKEYDOWN_ATTRIBUTE = addAttribute("ONKEYDOWN", HTML.Attribute.SCRIPT_TYPE),
- ONKEYPRESS_ATTRIBUTE = addAttribute("ONKEYPRESS", HTML.Attribute.SCRIPT_TYPE),
- ONKEYUP_ATTRIBUTE = addAttribute("ONKEYUP", HTML.Attribute.SCRIPT_TYPE),
- ONLOAD_ATTRIBUTE = addAttribute("ONLOAD", HTML.Attribute.SCRIPT_TYPE),
- ONMOUSEDOWN_ATTRIBUTE = addAttribute("ONMOUSEDOWN", HTML.Attribute.SCRIPT_TYPE),
- ONMOUSEMOVE_ATTRIBUTE = addAttribute("ONMOUSEMOVE", HTML.Attribute.SCRIPT_TYPE),
- ONMOUSEOUT_ATTRIBUTE = addAttribute("ONMOUSEOUT", HTML.Attribute.SCRIPT_TYPE),
- ONMOUSEOVER_ATTRIBUTE = addAttribute("ONMOUSEOVER", HTML.Attribute.SCRIPT_TYPE),
- ONMOUSEUP_ATTRIBUTE = addAttribute("ONMOUSEUP", HTML.Attribute.SCRIPT_TYPE),
- ONRESET_ATTRIBUTE = addAttribute("ONRESET", HTML.Attribute.SCRIPT_TYPE),
- ONSELECT_ATTRIBUTE = addAttribute("ONSELECT", HTML.Attribute.SCRIPT_TYPE),
- ONSUBMIT_ATTRIBUTE = addAttribute("ONSUBMIT", HTML.Attribute.SCRIPT_TYPE),
- ONUNLOAD_ATTRIBUTE = addAttribute("ONUNLOAD", HTML.Attribute.SCRIPT_TYPE),
- PROFILE_ATTRIBUTE = addAttribute("PROFILE", HTML.Attribute.URI_TYPE),
- PROMPT_ATTRIBUTE = addAttribute("PROMPT"),
- READONLY_ATTRIBUTE = addAttribute("READONLY", HTML.Attribute.BOOLEAN_TYPE),
- REL_ATTRIBUTE = addAttribute("REL"),
- REV_ATTRIBUTE = addAttribute("REV"),
- ROWS_ATTRIBUTE = addAttribute("ROWS"),
- ROWSPAN_ATTRIBUTE = addAttribute("ROWSPAN"),
- RULES_ATTRIBUTE = addAttribute("RULES"),
- SCHEME_ATTRIBUTE = addAttribute("SCHEME"),
- SCOPE_ATTRIBUTE = addAttribute("SCOPE"),
- SCROLLING_ATTRIBUTE = addAttribute("SCROLLING",
- HTML.Attribute.ENUM_TYPE,
- new String[] {"yes", "no", "auto"}),
- SELECTED_ATTRIBUTE = addAttribute("SELECTED", HTML.Attribute.BOOLEAN_TYPE),
- SHAPE_ATTRIBUTE = addAttribute("SHAPE"),
- SIZE_ATTRIBUTE = addAttribute("SIZE"),
- SPAN_ATTRIBUTE = addAttribute("SPAN"),
- SRC_ATTRIBUTE = addAttribute("SRC", HTML.Attribute.URI_TYPE),
- STANDBY_ATTRIBUTE = addAttribute("STANDBY"),
- START_ATTRIBUTE = addAttribute("START"),
- STYLE_ATTRIBUTE = addAttribute("STYLE"),
- SUMMARY_ATTRIBUTE = addAttribute("SUMMARY"),
- TABINDEX_ATTRIBUTE = addAttribute("TABINDEX"),
- TARGET_ATTRIBUTE = addAttribute("TARGET"),
- TEXT_ATTRIBUTE = addAttribute("TEXT"),
- TITLE_ATTRIBUTE = addAttribute("TITLE"),
- TYPE_ATTRIBUTE = addAttribute("TYPE"),
- USEMAP_ATTRIBUTE = addAttribute("USEMAP", HTML.Attribute.URI_TYPE),
- VALIGN_ATTRIBUTE = addAttribute("VALIGN",
- HTML.Attribute.ENUM_TYPE,
- new String[] {"top", "middle", "bottom", "baseline"}),
- VALUE_ATTRIBUTE = addAttribute("VALUE"),
- VALUETYPE_ATTRIBUTE = addAttribute("VALUETYPE",
- HTML.Attribute.ENUM_TYPE,
- new String[] {"data", "ref", "object"}),
- VERSION_ATTRIBUTE = addAttribute("VERSION"),
- VLINK_ATTRIBUTE = addAttribute("VLINK"),
- VSPACE_ATTRIBUTE = addAttribute("VSPACE"),
- WIDTH_ATTRIBUTE = addAttribute("WIDTH");
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/html/parser/HtmlDocument.java b/src/com/android/mail/lib/html/parser/HtmlDocument.java
deleted file mode 100644
index 09a6bbc..0000000
--- a/src/com/android/mail/lib/html/parser/HtmlDocument.java
+++ /dev/null
@@ -1,1272 +0,0 @@
-/**
- * Copyright (c) 2004, 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.mail.lib.html.parser;
-
-import com.android.mail.lib.base.CharEscapers;
-import com.android.mail.lib.base.CharMatcher;
-import com.android.mail.lib.base.StringUtil;
-import com.android.mail.lib.base.X;
-import com.google.common.collect.Lists;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-
-/**
- * HtmlDocument is a container for a list of html nodes, and represents the
- * entire html document. It contains toHTML() method which prints out the html
- * text, toXHTML for printing out XHTML text and toString() which prints out in
- * debug format.
- *
- * @author jlim@google.com (Jing Yee Lim)
- */
-public class HtmlDocument {
- /** List of Node objects */
- private final List<Node> nodes;
-
- /**
- * Creates a Html document.
- * @param nodes list of html nodes
- */
- public HtmlDocument(List<Node> nodes) {
- this.nodes = nodes;
- }
-
- /** Gets the list of nodes */
- public List<Node> getNodes() {
- return nodes;
- }
-
- /** Returns a HTML string for the current document */
- public String toHTML() {
- StringBuilder sb = new StringBuilder(nodes.size() * 10);
- for (Node n : nodes) {
- n.toHTML(sb);
- }
- return sb.toString();
- }
-
- /** Returns a XHTML string for the current document */
- public String toXHTML() {
- StringBuilder sb = new StringBuilder(nodes.size() * 10);
- for (Node n : nodes) {
- n.toXHTML(sb);
- }
- return sb.toString();
- }
-
- /**
- * Returns, as much as possible, original content of preparsed nodes. This
- * is only different from toHTML() if the nodes were created with original
- * content, e.g., by HtmlParser in preserve mode.
- */
- public String toOriginalHTML() {
- StringBuilder sb = new StringBuilder(nodes.size() * 10);
- for (Node n : nodes) {
- n.toOriginalHTML(sb);
- }
- return sb.toString();
- }
-
- /** Returns the HTML document in debug format */
- @Override
- public String toString() {
- StringWriter strWriter = new StringWriter();
- accept(new DebugPrinter(new PrintWriter(strWriter)));
- return strWriter.toString();
- }
-
- /**
- * Creates start Tag Node.
- * @see HtmlDocument#createTag(HTML.Element, List, String, String)
- */
- public static Tag createTag(HTML.Element element, List<TagAttribute> attributes) {
- return createTag(element, attributes, null, null);
- }
-
- /**
- * Creates start Tag Node.
- * @see HtmlDocument.Tag#Tag(HTML.Element, List, boolean, String, String)
- */
- public static Tag createTag(HTML.Element element,
- List<TagAttribute> attributes, String originalHtmlBeforeAttributes,
- String originalHtmlAfterAttributes) {
- return new Tag(element, attributes, false, originalHtmlBeforeAttributes,
- originalHtmlAfterAttributes);
- }
-
- /**
- * Creates self-terminating Tag Node.
- * @see HtmlDocument#createSelfTerminatingTag(HTML.Element, List, String, String)
- */
- public static Tag createSelfTerminatingTag(HTML.Element element,
- List<TagAttribute> attributes) {
- return createSelfTerminatingTag(element, attributes, null, null);
- }
-
- /**
- * Creates self-terminating Tag Node.
- * @see HtmlDocument#createTag(HTML.Element, List, String, String)
- */
- public static Tag createSelfTerminatingTag(HTML.Element element,
- List<TagAttribute> attributes, String originalHtmlBeforeAttributes,
- String originalHtmlAfterAttributes) {
- return new Tag(element, attributes, true, originalHtmlBeforeAttributes,
- originalHtmlAfterAttributes);
- }
-
- /**
- * @see HtmlDocument#createEndTag(HTML.Element, String)
- */
- public static EndTag createEndTag(HTML.Element element) {
- return createEndTag(element, null);
- }
-
- /**
- * @see HtmlDocument.EndTag#EndTag(HTML.Element, String)
- */
- public static EndTag createEndTag(HTML.Element element, String originalHtml) {
- return new EndTag(element, originalHtml);
- }
-
- /**
- * @see HtmlDocument#createTagAttribute(HTML.Attribute, String, String)
- */
- public static TagAttribute createTagAttribute(HTML.Attribute attr, String value) {
- return createTagAttribute(attr, value, null);
- }
-
- /**
- * @see HtmlDocument.TagAttribute#TagAttribute(HTML.Attribute, String, String)
- */
- public static TagAttribute createTagAttribute(HTML.Attribute attr,
- String value, String originalHtml) {
- X.assertTrue(attr != null);
- return new TagAttribute(attr, value, originalHtml);
- }
-
- /**
- * @see HtmlDocument#createText(String, String)
- */
- public static Text createText(String text) {
- return createText(text, null);
- }
-
- /**
- * Creates a Text node.
- * @see UnescapedText#UnescapedText(String, String)
- */
- public static Text createText(String text, String original) {
- return new UnescapedText(text, original);
- }
-
- /**
- * Creates a Text node where the content hasn't been unescaped yet (this will
- * be done lazily).
- */
- public static Text createEscapedText(String htmlText, String original) {
- return new EscapedText(htmlText, original);
- }
-
- /**
- * Creates an Comment node.
- * @see Comment#Comment(String)
- */
- public static Comment createHtmlComment(String content) {
- return new Comment(content);
- }
-
- /**
- * Creates a CDATA node.
- * @see CDATA#CDATA(String)
- */
- public static CDATA createCDATA(String text) {
- return new CDATA(text);
- }
-
- /** Accepts a Visitor */
- public void accept(Visitor v) {
- v.start();
- for (Node node : nodes) {
- node.accept(v);
- }
- v.finish();
- }
-
- /**
- * @param filter results of this filter replace the existing nodes
- * @return new document with filtered nodes
- */
- public HtmlDocument filter(MultiplexFilter filter) {
- filter.start();
- List<Node> newNodes = new ArrayList<Node>();
- for (Node node : nodes) {
- filter.filter(node, newNodes);
- }
- filter.finish(newNodes);
- return new HtmlDocument(newNodes);
- }
-
- /**
- * Html node
- */
- public static abstract class Node {
-
- /** Accepts a visitor */
- public abstract void accept(Visitor visitor);
-
- /** Converts to HTML */
- public String toHTML() {
- StringBuilder sb = new StringBuilder();
- toHTML(sb);
- return sb.toString();
- }
-
- /** Converts to HTML */
- public abstract void toHTML(StringBuilder sb);
-
- /** Converts to XHTML */
- public String toXHTML() {
- StringBuilder sb = new StringBuilder();
- toXHTML(sb);
- return sb.toString();
- }
-
- /** Converts to XHTML */
- public abstract void toXHTML(StringBuilder sb);
-
- /**
- * @return Original if it's available; otherwise, returns
- * <code>toHTML()</code>
- */
- public String toOriginalHTML() {
- StringBuilder sb = new StringBuilder();
- toOriginalHTML(sb);
- return sb.toString();
- }
-
- /**
- * @param sb Destination of HTML to be appended. Appends original if it's
- * available; otherwise, appends <code>toHTML()</code>
- */
- public abstract void toOriginalHTML(StringBuilder sb);
- }
-
- /**
- * HTML comment node.
- */
- public static class Comment extends Node {
-
- private final String content;
-
- /**
- * @param content Raw comment, including "<!--" and "-->".
- */
- public Comment(String content) {
- this.content = content;
- }
-
- @Override
- public void accept(Visitor visitor) {
- visitor.visitComment(this);
- }
-
- /**
- * Emit original unchanged.
- * @param sb Destination of result.
- */
- @Override
- public void toHTML(StringBuilder sb) {
- sb.append(content);
- }
-
- /**
- * Emit original unchanged.
- * @param sb Destination of result.
- */
- @Override
- public void toXHTML(StringBuilder sb) {
- sb.append(content);
- }
-
- /**
- * Emit original unchanged.
- * @param sb Destination of result.
- */
- @Override
- public void toOriginalHTML(StringBuilder sb) {
- sb.append(content);
- }
-
- /**
- * @return Original unchanged.
- */
- public String getContent() {
- return content;
- }
- }
-
- /**
- * Text node
- */
- public static abstract class Text extends Node {
-
- /**
- * unaltered original content of this node
- */
- private final String originalHtml;
-
- /**
- * content of this node in HTML format
- */
- private String html;
-
- /**
- * @param originalHtml Unaltered original HTML. If not null,
- * toOriginalHTML() will return this.
- */
- protected Text(String originalHtml) {
- this.originalHtml = originalHtml;
- }
-
- /**
- * Gets the plain, unescaped text.
- */
- abstract public String getText();
-
- // Returns true if it contains only white space
- public boolean isWhitespace() {
- String text = getText();
- int len = text.length();
- for (int i = 0; i < len; i++) {
- if (!Character.isWhitespace(text.charAt(i))) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (o instanceof Text) {
- Text that = (Text) o;
-
- return this.originalHtml == null ? that.originalHtml == null
- : this.originalHtml.equals(that.originalHtml);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return originalHtml == null ? 0 : originalHtml.hashCode();
- }
-
- @Override
- public String toString() {
- return getText();
- }
-
- /** Extends Node.accept */
- @Override
- public void accept(Visitor visitor) {
- visitor.visitText(this);
- }
-
- /**
- * Gets the HTML, with HTML entities escaped.
- */
- @Override
- public void toHTML(StringBuilder sb) {
- if (html == null) {
- html = CharEscapers.asciiHtmlEscaper().escape(getText());
- }
- sb.append(html);
- }
-
- /**
- * @see HtmlDocument.Text#toHTML(StringBuilder)
- */
- @Override
- public void toXHTML(StringBuilder sb) {
- toHTML(sb);
- }
-
- /**
- * @param sb Appends original HTML to this if available. Otherwise,
- * same as toHTML().
- */
- @Override
- public void toOriginalHTML(StringBuilder sb) {
- if (originalHtml != null) {
- sb.append(originalHtml);
- } else {
- toHTML(sb);
- }
- }
-
- /**
- * @return the original HTML (possibly with entities unescaped if the
- * document was malformed). May be null if original HTML was not preserved
- * (see constructor argument of {@link HtmlParser})
- */
- public String getOriginalHTML() {
- return originalHtml;
- }
- }
-
- /**
- * {@link Text} implementation where the given text is assumed to have been
- * already HTML unescaped.
- */
- private static class UnescapedText extends Text {
- /**
- * content of this node as plain, unescaped text
- */
- protected final String text;
-
- private UnescapedText(String plainText, String originalHtml) {
- super(originalHtml);
- X.assertTrue(plainText != null);
- this.text = plainText;
- }
-
- @Override public String getText() {
- return text;
- }
- }
-
- /**
- * {@link Text} implementation where the given text is not unescaped yet, and
- * unescaping will only be done lazily.
- */
- private static class EscapedText extends Text {
- private final String htmlText;
- private String text;
-
- private EscapedText(String htmlText, String originalHtml) {
- super(originalHtml);
- this.htmlText = htmlText;
- }
-
- @Override public String getText() {
- if (text == null) {
- text = StringUtil.unescapeHTML(htmlText);
- }
- return text;
- }
- }
-
- /**
- * CDATA node is a subclass of Text node.
- */
- public static class CDATA extends UnescapedText {
- private CDATA(String text) {
- super(text, text);
- }
-
- @Override public void toHTML(StringBuilder sb) {
- // Do not htmlescape CDATA text
- sb.append(text);
- }
-
- @Override public void toXHTML(StringBuilder sb) {
- sb.append("<![CDATA[")
- .append(text)
- .append("]]>");
- }
- }
-
- /**
- * Tag is a HTML open tag.
- */
- public static class Tag extends Node {
- // The element
- private final HTML.Element element;
-
- // List of TagAttribute objects. This may be null.
- private List<TagAttribute> attributes;
-
- private final boolean isSelfTerminating;
-
- private final String originalHtmlBeforeAttributes;
-
- private final String originalHtmlAfterAttributes;
-
- /**
- * @param element the HTML4 element
- * @param attributes list of TagAttribute objects, may be null
- * @param isSelfTerminating
- * @param originalHtmlBeforeAttributes Original tag's full content before
- * first attribute, including beginning '<'. This should not
- * include preceeding whitespace for the first attribute, as that
- * should be included in the attribute node. If not null, tag will
- * preserve this original content. e.g., if original tag were
- * "<foO bar='zbc'>", case of foO would be preserved. This
- * method does not validate that
- * <code>originalHtmlBeforeAttributes</code> is a valid tag String.
- * @param originalHtmlAfterAttributes Full content of original tag after
- * last attribute, including ending '>'. If not null, tag will
- * preserve this original content. e.g., if original tag were
- * "<foo bar='zbc' >", the spaces before '>' be preserved.
- * This method does not validate that
- * <code>originalHtmlAfterAttributes</code> is a valid tag String.
- */
- private Tag(HTML.Element element, List<TagAttribute> attributes,
- boolean isSelfTerminating, String originalHtmlBeforeAttributes,
- String originalHtmlAfterAttributes) {
- X.assertTrue(element != null);
- this.element = element;
- this.attributes = attributes;
- this.isSelfTerminating = isSelfTerminating;
- this.originalHtmlBeforeAttributes = originalHtmlBeforeAttributes;
- this.originalHtmlAfterAttributes = originalHtmlAfterAttributes;
- }
-
- /** Gets the name */
- public String getName() {
- return element.getName();
- }
-
- /** Gets the element */
- public HTML.Element getElement() {
- return element;
- }
-
- /** Adds an attribute */
- public void addAttribute(HTML.Attribute attr, String value) {
- X.assertTrue(attr != null);
- addAttribute(new TagAttribute(attr, value, null));
- }
-
- /** Adds an attribute */
- public void addAttribute(TagAttribute attr) {
- X.assertTrue(attr != null);
- if (attributes == null) {
- attributes = new ArrayList<TagAttribute>();
- }
- attributes.add(attr);
- }
-
- /** Gets the list of attributes, note that this maybe null. */
- public List<TagAttribute> getAttributes() {
- return attributes;
- }
-
- /** Finds and returns a TagAttribute, or null if not found */
- public TagAttribute getAttribute(HTML.Attribute attr) {
- if (attributes != null) {
- for (TagAttribute attribute : attributes) {
- if (attribute.getAttribute().equals(attr)) {
- return attribute;
- }
- }
- }
- return null;
- }
-
- /**
- * Finds and returns list of TagAttribute of given attribute
- * type, or empty list if not found,
- */
- public List<TagAttribute> getAttributes(HTML.Attribute attr) {
- List<TagAttribute> result = Lists.newArrayList();
- if (attributes != null) {
- for (TagAttribute attribute : attributes) {
- if (attribute.getAttribute().equals(attr)) {
- result.add(attribute);
- }
- }
- }
- return result;
- }
-
- /** Returns debug string */
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("Start Tag: ");
- sb.append(element.getName());
- if (attributes != null) {
- for (TagAttribute attr : attributes) {
- sb.append(' ');
- sb.append(attr.toString());
- }
- }
- return sb.toString();
- }
-
- /** Implements Node.accept */
- @Override
- public void accept(Visitor visitor) {
- visitor.visitTag(this);
- }
-
- /** Implements Node.toHTML */
- @Override
- public void toHTML(StringBuilder sb) {
- serialize(sb, SerializeType.HTML);
- }
-
- @Override
- public void toXHTML(StringBuilder sb) {
- serialize(sb, SerializeType.XHTML);
- }
-
- @Override
- public void toOriginalHTML(StringBuilder sb) {
- serialize(sb, SerializeType.ORIGINAL_HTML);
- }
-
- /**
- * Specifies format of serialized output.
- */
- private enum SerializeType {
- ORIGINAL_HTML, HTML, XHTML
- }
-
- private void serialize(StringBuilder sb, SerializeType type) {
- // before attributes
- if (type == SerializeType.ORIGINAL_HTML && originalHtmlBeforeAttributes != null) {
- sb.append(originalHtmlBeforeAttributes);
- } else {
- sb.append('<');
- sb.append(element.getName());
- }
-
- // attributes
- if (attributes != null) {
- for (TagAttribute attr : attributes) {
- // attribute includes leading whitespace, so we needn't add it here
- if (type == SerializeType.ORIGINAL_HTML) {
- attr.toOriginalHTML(sb);
- } else if (type == SerializeType.HTML) {
- attr.toHTML(sb);
- } else {
- attr.toXHTML(sb);
- }
- }
- }
-
- // after attributes
- if (type == SerializeType.ORIGINAL_HTML && originalHtmlAfterAttributes != null) {
- sb.append(originalHtmlAfterAttributes);
- } else if (type == SerializeType.XHTML && (isSelfTerminating || getElement().isEmpty())) {
- sb.append(" />");
- } else {
- sb.append('>');
- }
- }
-
- public boolean isSelfTerminating() {
- return isSelfTerminating;
- }
-
- public String getOriginalHtmlBeforeAttributes() {
- return originalHtmlBeforeAttributes;
- }
-
- public String getOriginalHtmlAfterAttributes() {
- return originalHtmlAfterAttributes;
- }
- }
-
- /**
- * EndTag is a closing HTML tag.
- */
- public static class EndTag extends Node {
- // The element
- private final HTML.Element element;
-
- private final String originalHtml;
-
- /**
- * @param element The HTML.Element element. Can not be null.
- * @param originalHtml Full content of original tag, including beginning
- * and ending '<' and '>'. If not null, tag will preserve this original
- * content. e.g., if original tag were "</foo >", the space after foo
- * would be preserved. This method does not validate that originalHtml is a
- * valid tag String.
- */
- private EndTag(HTML.Element element, String originalHtml) {
- X.assertTrue(element != null);
- this.element = element;
- this.originalHtml = originalHtml;
- }
-
- /** Gets the name */
- public String getName() {
- return element.getName();
- }
-
- /** Gets the element */
- public HTML.Element getElement() {
- return element;
- }
-
- /** Returns debug string */
- @Override
- public String toString() {
- return "End Tag: " + element.getName();
- }
-
- /** Implements Node.accept */
- @Override
- public void accept(Visitor visitor) {
- visitor.visitEndTag(this);
- }
-
- /** Implements Node.toHTML */
- @Override
- public void toHTML(StringBuilder sb) {
- sb.append("</");
- sb.append(element.getName());
- sb.append('>');
- }
-
- @Override
- public void toXHTML(StringBuilder sb) {
- toHTML(sb);
- }
-
- @Override
- public void toOriginalHTML(StringBuilder sb) {
- if (originalHtml != null) {
- sb.append(originalHtml);
- } else {
- toHTML(sb);
- }
- }
- }
-
- /**
- * TagAttribute represents an attribute in a HTML tag.
- */
- public static class TagAttribute {
- private final HTML.Attribute attribute;
- private String value;
- private String originalHtml;
-
- /**
- * @param attribute the HTML.Attribute. Can't be null.
- * @param value The value in plain-text format. This can be null if the
- * attribute has no value.
- * @param originalHtml If not null, toOriginalHTML() will preserve original
- * content. This should contain any leading whitespace from the
- * original.
- */
- private TagAttribute(HTML.Attribute attribute, String value, String originalHtml) {
- X.assertTrue(attribute != null);
- this.attribute = attribute;
- this.value = value;
- this.originalHtml = originalHtml;
- }
-
- /** Gets the name */
- public String getName() {
- return attribute.getName();
- }
-
- /** Gets the HTML.Attribute information */
- public HTML.Attribute getAttribute() {
- return attribute;
- }
-
- /**
- * Sets the attribute value.
- * This value must be in plain-text, not html-escaped.
- * This can be null, if the attribute has no values.
- * This clears <code>originalHtml_</code> if it were set, so
- * <code>toOriginalHTML()</code> might not preserve original any more.
- */
- public void setValue(String value) {
- this.value = value;
- originalHtml = null;
- }
-
- /** Returns the attribute value in plain-text, never null */
- public String getValue() {
- return value != null ? value : "";
- }
-
- /** Returns true if the attribute value is not empty */
- public boolean hasValue() {
- return value != null;
- }
-
- /**
- * Writes out the attribute in HTML format with all necessary preceding
- * whitespace. Emits originalHtml_ if it were specified to the constructor.
- * Otherwise, emits a new name="value" string with a single preceding space.
- */
- public void toHTML(StringBuilder sb) {
- sb.append(' ');
- sb.append(attribute.getName());
- if (value != null && attribute.getType() != HTML.Attribute.BOOLEAN_TYPE) {
- sb.append("=\"");
- sb.append(CharEscapers.asciiHtmlEscaper().escape(value));
- sb.append("\"");
- }
- }
-
- /** Returns the attribute html string */
- public String toHTML() {
- StringBuilder sb = new StringBuilder();
- toHTML(sb);
- return sb.toString();
- }
-
- /**
- * Writes out the attribute in XHTML format (value is always appended,
- * even if it is empty) with all necessary preceeding whitespace.
- */
- public void toXHTML(StringBuilder sb) {
- sb.append(' ');
- sb.append(attribute.getName()).append("=\"");
-
- // Assume that value-less attribute are boolean attributes like "disabled"
- if (hasValue()) {
- sb.append(CharEscapers.asciiHtmlEscaper().escape(value));
- } else {
- sb.append(attribute.getName());
- }
-
- sb.append("\"");
- }
-
- /** Returns the attribute XHTML string */
- public String toXHTML() {
- StringBuilder sb = new StringBuilder();
- toXHTML(sb);
- return sb.toString();
- }
-
- /**
- * @param sb Destination to which attribute is written, in its original
- * preparsed form if possible.
- */
- public void toOriginalHTML(StringBuilder sb) {
- if (originalHtml != null) {
- sb.append(originalHtml);
- } else {
- toHTML(sb);
- }
- }
-
- /**
- * Writes out the attribute in its original form as it was parsed..
- */
- public String toOriginalHTML() {
- StringBuilder sb = new StringBuilder();
- toOriginalHTML(sb);
- return sb.toString();
- }
-
- @Override
- public String toString() {
- return "{" + attribute.getName() + "=" + value + "}";
- }
- }
-
- /**
- * Filter is like Visitor, except it implies that the nodes may be changed,
- * whereas HtmlDocument.Visitor just implies that the nodes are iterated
- * over. A Filter can behave just like a Visitor if it merely returns the
- * same node that it visited. Also, methods may be called on a node to change
- * the values it contains. Alternatively, a new node entirely can be created
- * and returned, which will essentially replace the previous node with the
- * new node in the document tree. A node may be removed by returning null
- * instead of a node.
- */
- public static interface Filter {
- /** This is called first */
- void start();
-
- /** A text node */
- Text visitText(Text n);
-
- /** An open tag */
- Tag visitTag(Tag n);
-
- /** End tag */
- EndTag visitEndTag(EndTag n);
-
- /** HTML comment */
- Comment visitComment(Comment n);
-
- /* Called at the end. */
- void finish();
- }
-
- /**
- * Like Filter, except each node may be replaced by multiple nodes. Also,
- * does not do double dispatch accept/visit.
- */
- public static interface MultiplexFilter {
- /**
- * Called first.
- */
- void start();
-
- /**
- * @param originalNode node to filter
- * @param out Destination to which this object appends nodes to replace
- * originalNode. Can not be null.
- */
- void filter(Node originalNode, List<Node> out);
-
- /**
- * Called at the end.
- * @param out Destination to which this object appends nodes at the end of
- * the document. Can not be null.
- */
- void finish(List<Node> out);
- }
-
- /**
- * Converts a normal {@link Filter} into a {@link MultiplexFilter}.
- */
- public static class MultiplexFilterAdapter implements MultiplexFilter {
-
- private final Filter filter;
-
- public MultiplexFilterAdapter(Filter filter) {
- this.filter = filter;
- }
-
- public void start() {
- filter.start();
- }
-
- public void filter(Node originalNode, List<Node> out) {
- if (originalNode == null) {
- return;
- }
-
- Node resultNode;
- if (originalNode instanceof Tag) {
- resultNode = filter.visitTag((Tag) originalNode);
- } else if (originalNode instanceof Text) {
- resultNode = filter.visitText((Text) originalNode);
- } else if (originalNode instanceof EndTag) {
- resultNode = filter.visitEndTag((EndTag) originalNode);
- } else if (originalNode instanceof Comment) {
- resultNode = filter.visitComment((Comment) originalNode);
- } else {
- throw new IllegalArgumentException("unknown node type: " + originalNode.getClass());
- }
-
- if (resultNode != null) {
- out.add(resultNode);
- }
- }
-
- public void finish(List<Node> out) {
- filter.finish();
- }
- }
-
- /**
- * Like Filter, except each node may be replaced by multiple nodes. Also,
- * does not do double dispatch accept/visit. Dispatches filterNode() to
- * node-specific methods.
- */
- public static abstract class SimpleMultiplexFilter implements MultiplexFilter {
-
- /**
- * @see HtmlDocument.MultiplexFilter#filter(HtmlDocument.Node, List)
- */
- public void filter(Node originalNode, List<Node> out) {
- if (originalNode == null) {
- return;
- }
-
- if (originalNode instanceof Tag) {
- filterTag((Tag) originalNode, out);
- } else if (originalNode instanceof Text) {
- filterText((Text) originalNode, out);
- } else if (originalNode instanceof EndTag) {
- filterEndTag((EndTag) originalNode, out);
- } else if (originalNode instanceof Comment) {
- filterComment((Comment) originalNode, out);
- } else {
- throw new IllegalArgumentException("unknown node type: "
- + originalNode.getClass());
- }
- }
-
- public abstract void filterTag(Tag originalTag, List<Node> out);
-
- public abstract void filterText(Text originalText, List<Node> out);
-
- public abstract void filterEndTag(EndTag originalEndTag, List<Node> out);
-
- public void filterComment(Comment originalComment, List<Node> out) {
- }
- }
-
- /**
- * Contains a list of filters which are applied, in order, to each Node. The
- * output of each becomes the input to the next. As soon as one returns an
- * empty list it breaks the chain.
- */
- public static class MultiplexFilterChain implements MultiplexFilter {
-
- private final List<MultiplexFilter> filters = new ArrayList<MultiplexFilter>();
-
- /**
- * @param sourceFilters these filters are applied in List order
- */
- public MultiplexFilterChain(List<MultiplexFilter> sourceFilters) {
- filters.addAll(sourceFilters);
- }
-
- /**
- * @see HtmlDocument.MultiplexFilter#start()
- */
- public void start() {
- for (MultiplexFilter filter : filters) {
- filter.start();
- }
- }
-
- /**
- * @see HtmlDocument.MultiplexFilter#filter(HtmlDocument.Node, List)
- */
- public void filter(Node originalNode, List<Node> out) {
- List<Node> result = new ArrayList<Node>();
- result.add(originalNode);
-
- // loop through filters until one returns nothing, or until we're out of
- // filters
- for (MultiplexFilter filter : filters) {
- if (result.isEmpty()) {
- return;
- }
-
- // apply filter to each node and collect results
- List<Node> newResult = new ArrayList<Node>();
- for (Node node : result) {
- filter.filter(node, newResult);
- }
- result = newResult;
- }
-
- out.addAll(result);
- }
-
- /**
- * @see HtmlDocument.MultiplexFilter#finish(List)
- */
- public void finish(List<Node> out) {
- List<Node> result = new ArrayList<Node>();
-
- // loop through filters until one returns nothing, or until we're out of
- // filters
- for (MultiplexFilter filter : filters) {
- // apply filter to each node and collect results
- List<Node> newResult = new ArrayList<Node>();
- for (Node node : result) {
- filter.filter(node, newResult);
- }
- filter.finish(newResult);
- result = newResult;
- }
-
- out.addAll(result);
- }
- }
-
- /**
- * Html visitor allows external code to iterate through the nodes in the
- * document. See HtmlDocument.accept.
- */
- public static interface Visitor {
- /** This is called first */
- void start();
-
- /** A text node */
- void visitText(Text n);
-
- /** An open tag */
- void visitTag(Tag n);
-
- /** End tag */
- void visitEndTag(EndTag n);
-
- /** comment */
- void visitComment(Comment n);
-
- /* Called at the end. */
- void finish();
- }
-
- /**
- * An implementation of the Visitor interface which simply delegates its
- * methods to a wrapped instance of another Visitor.
- *
- * <p>This is useful for chaining Visitors together.
- */
- public static class VisitorWrapper implements Visitor {
- private final Visitor wrapped;
-
- protected VisitorWrapper(Visitor wrap) {
- wrapped = wrap;
- }
-
- public void start() {
- wrapped.start();
- }
-
- public void visitText(Text n) {
- wrapped.visitText(n);
- }
-
- public void visitTag(Tag n) {
- wrapped.visitTag(n);
- }
-
- public void visitEndTag(EndTag n) {
- wrapped.visitEndTag(n);
- }
-
- public void visitComment(Comment n) {
- wrapped.visitComment(n);
- }
-
- public void finish() {
- wrapped.finish();
- }
- }
-
- /**
- * A special helper Visitor that builds a HtmlDocument.
- */
- public static class Builder implements Visitor {
- private final boolean preserveComments;
- private final List<Node> nodes = new ArrayList<Node>();
- private HtmlDocument doc;
-
- /**
- * @see Builder#Builder(boolean)
- */
- public Builder() {
- this(false);
- }
-
- /**
- * @param preserveComments If false, ignores Comment nodes
- */
- public Builder(boolean preserveComments) {
- this.preserveComments = preserveComments;
- }
-
- public void addNode(Node node) {
- nodes.add(node);
- }
- public void start() {
- }
- public void visitText(Text t) {
- addNode(t);
- }
- public void visitTag(Tag t) {
- addNode(t);
- }
- public void visitComment(Comment n) {
- if (preserveComments) {
- addNode(n);
- }
- }
- public void visitEndTag(EndTag t) {
- addNode(t);
- }
- public void finish() {
- doc = new HtmlDocument(nodes);
- }
-
- /** Gets the html document that has been constructed */
- public HtmlDocument getDocument() {
- return doc;
- }
- }
-
- /**
- * A Visitor that prints out the html document in debug format.
- */
- public static class DebugPrinter implements Visitor {
-
- private final PrintWriter writer;
-
- public DebugPrinter(PrintWriter writer) {
- this.writer = writer;
- }
-
- public void start() {
- }
-
- public void visitText(Text t) {
- writeCollapsed("TEXT", t.getText());
- }
-
- public void visitComment(Comment n) {
- writeCollapsed("COMMENT", n.getContent());
- }
-
- private void writeCollapsed(String type, String s) {
- writer.print(type);
- writer.print(": ");
- String noNewlines = s.replace("\n", " ");
- // Use CharMatcher#WHITESPACE?
- String collapsed = CharMatcher.LEGACY_WHITESPACE.trimAndCollapseFrom(noNewlines, ' ');
- writer.print(collapsed);
- }
-
- public void visitTag(Tag tag) {
- writer.print("==<" + tag.getName() + ">");
- List<TagAttribute> attributes = tag.getAttributes();
- if (attributes != null) {
-
- // Attribute values
- List<String> attrs = new ArrayList<String>();
- for (TagAttribute a : attributes) {
- attrs.add("[" + a.getName() + " : " + a.getValue() + "]");
- }
- String[] array = attrs.toArray(new String[attrs.size()]);
-
- // Sort the attributes so that it's easier to read and compare
- Arrays.sort(array);
- for (int i = 0; i < array.length; i++) {
- writer.print(" " + array[i]);
- }
- }
- writer.println();
- }
-
- public void visitEndTag(EndTag endtag) {
- writer.println("==</" + endtag.getName() + ">");
- }
-
- public void finish() {
- }
- }
-
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/html/parser/HtmlParser.java b/src/com/android/mail/lib/html/parser/HtmlParser.java
deleted file mode 100644
index a501c85..0000000
--- a/src/com/android/mail/lib/html/parser/HtmlParser.java
+++ /dev/null
@@ -1,1131 +0,0 @@
-/**
- * Copyright (c) 2004, 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.mail.lib.html.parser;
-
-import com.android.mail.lib.base.CharEscapers;
-import com.android.mail.lib.base.CharMatcher;
-import com.android.mail.lib.base.Preconditions;
-import com.android.mail.lib.base.StringUtil;
-import com.android.mail.lib.base.X;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.io.ByteStreams;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * HtmlParser is a simple but efficient html parser.
- * - It's simple because it does not do incremental parsing like some other
- * parser. It assumes that the entire html text is available.
- * - It offers 3 levels of aggressiveness in correcting errors in HTML (see
- * HtmlParser.ParseStyle).
- * - HTML comments are ignored unless initialized with ParseStyle.PRESERVE_ALL.
- */
-public class HtmlParser {
-
- // States
- private enum State {
- IN_TEXT, IN_TAG, IN_COMMENT, IN_CDATA
- }
-
- // The current state
- private State state;
-
- private int clipLength = Integer.MAX_VALUE;
- private boolean clipped;
-
- // The html text
- private String html;
-
- // The entire array of nodes
- private List<HtmlDocument.Node> nodes;
-
- // Turn on for debug information.
- private static boolean DEBUG = false;
-
- // Default whitelist
- public static final HtmlWhitelist DEFAULT_WHITELIST = HTML4.getWhitelist();
-
- // Whitelists for looking up accepted HTML tags and attributes
- private List<HtmlWhitelist> whitelists = Lists.newArrayList(DEFAULT_WHITELIST);
-
- /**
- * This setting controls how much of the original HTML is preserved. In
- * ascending order of aggressiveness:
- * - PRESERVE_ALL: Preserves all of original content.
- * *** Warning - PRESERVE_ALL mode preserves invalid and unsafe HTML. ***
- * - PRESERVE_VALID: Preserves only valid visible HTML handled by
- * most browsers. Discards comments, unknown tags and attributes, and
- * nameless end tags. Encodes all '<' characters that aren't part of a tag.
- * - NORMALIZE: In addition to the changes made by PRESERVE_VALID, also
- * - unescapes then reescapes text to normalize entities
- * - normalizes whitespace and quotes around tag attributes
- */
- public enum ParseStyle { NORMALIZE, PRESERVE_VALID, PRESERVE_ALL }
-
- /**
- * True only in PRESERVE_ALL mode.
- * @see HtmlParser.ParseStyle
- */
- private final boolean preserveAll;
-
- /**
- * True in either PRESERVE_ALL or PRESERVE_VALID mode.
- * @see HtmlParser.ParseStyle
- */
- private final boolean preserveValidHtml;
-
- /**
- * @see HtmlParser#HtmlParser(HtmlParser.ParseStyle)
- */
- public HtmlParser() {
- this(ParseStyle.NORMALIZE);
- }
-
- /**
- * @param parseStyle Level of aggressiveness for how different
- * toHTML()/toXHTML() are from original.
- * @see HtmlParser.ParseStyle
- */
- public HtmlParser(ParseStyle parseStyle) {
- preserveAll = (parseStyle == ParseStyle.PRESERVE_ALL);
- preserveValidHtml = preserveAll || (parseStyle == ParseStyle.PRESERVE_VALID);
- }
-
- /**
- * Sets the maximum length, in characters, of an HTML message.
- *
- * @param clipLength must be greater than zero.
- * (It starts as Integer.MAX_VALUE)
- */
- public void setClipLength(int clipLength) {
- if (clipLength <= 0) {
- throw new IllegalArgumentException(
- "clipLength '" + clipLength + "' <= 0");
- }
- this.clipLength = clipLength;
- }
-
- public boolean isClipped() {
- return clipped;
- }
-
- /**
- * Sets the HTML whitelist. Calling this overrides any whitelist(s) that
- * the parser is configured to use. By default, the parser uses the standard
- * HTML4 whitelist.
- *
- * This has no effect in <code>ParseStyle.PRESERVE_ALL</code> mode.
- *
- * @param whitelist The whitelist to use. Must not be null.
- */
- public void setWhitelist(HtmlWhitelist whitelist) {
- Preconditions.checkNotNull(whitelist);
- whitelists = Lists.newArrayList(whitelist);
- }
-
- /**
- * Adds an HTML whitelist to the list of whitelists consulted when
- * processing an element or attribute. By default, the parser only uses
- * the standard HTML4 whitelist.
- *
- * Whitelists are consulted in reverse chronological order (starting from
- * the most recently added whitelist). The last whitelist consulted will
- * always be the standard HTML4 whitelist, unless this was overridden by
- * a call to {@link #setWhitelist}.
- *
- * This has no effect in <code>ParseStyle.PRESERVE_ALL</code> mode.
- *
- * @param whitelist The whitelist to use.
- */
- public void addWhitelist(HtmlWhitelist whitelist) {
- whitelists.add(whitelist);
- }
-
- /**
- * These are characters that we don't want to allow unquoted in an attribute
- * value because they might be interpreted by the browser as HTML control
- * characters. They are the 5 characters that are escaped by
- * com.google.common.base.CharEscapers.HTML_ESCAPE, plus '=' and whitespace.
- * Note that it shouldn't be possible for '>' or whitespace to be parsed as
- * part of an unquoted attribute value, but we leave them here for
- * completeness.
- * Package visibility for unit tests.
- */
- static Pattern NEEDS_QUOTING_ATTRIBUTE_VALUE_REGEX = Pattern.compile("[\"\'&<>=\\s]");
-
- //------------------------------------------------------------------------
- // Parsing
- //------------------------------------------------------------------------
-
- /**
- * Parses a String as HTML.
- *
- * @param html String to parse
- * @return an Html document
- */
- public HtmlDocument parse(String html) {
- this.html = html;
- // Use a LinkedList because we don't know the number of nodes ahead of
- // time. This will be compacted into an ArrayList in coalesceTextNodes().
- nodes = Lists.newLinkedList();
- state = State.IN_TEXT;
-
- clipped = false;
- int end = html.length();
- int clipEnd = Math.min(clipLength, end);
-
- for (int i = 0; i < end && !clipped;) {
-
- // At any one time, the parser is in one of these states:
- int pos;
- switch (state) {
- case IN_TEXT:
- // text will not attempt to parse beyond the clipping length
- pos = scanText(i, clipEnd);
- X.assertTrue(pos > i || state != State.IN_TEXT); // Must make progress.
- break;
-
- case IN_TAG:
- pos = scanTag(i, end);
- X.assertTrue(pos > i); // Must make progress
- break;
-
- case IN_COMMENT:
- pos = scanComment(i, end);
- state = State.IN_TEXT;
- X.assertTrue(pos > i); // Must make progress
- break;
-
- case IN_CDATA:
- pos = scanCDATA(i, end);
- X.assertTrue(pos > i || state != State.IN_CDATA); // Must make progress
- break;
-
- default:
- throw new Error("Unknown state!");
- }
-
- i = pos;
-
- // If we've reached or gone beyond the clipping length, stop.
- clipped = pos >= clipLength;
- }
-
- nodes = coalesceTextNodes(nodes);
-
- HtmlDocument doc = new HtmlDocument(nodes);
- nodes = null;
- html = null;
- return doc;
- }
-
- /**
- * During the course of parsing, we may have multiple adjacent Text nodes,
- * due to the sanitizer stripping out nodes between Text nodes. It is
- * important to coalesce them so that later steps in the pipeline can
- * treat the text as a single block (e.g. the step that inserts <wbr> tags).
- * @param nodes Original nodes.
- * @return Nodes with text nodes changed.
- */
- static List<HtmlDocument.Node> coalesceTextNodes(
- List<HtmlDocument.Node> nodes) {
- List<HtmlDocument.Node> out =
- new ArrayList<HtmlDocument.Node>(nodes.size());
- LinkedList<HtmlDocument.Text> textNodes = Lists.newLinkedList();
-
- for (HtmlDocument.Node node : nodes) {
- if (node instanceof HtmlDocument.Text) {
- textNodes.add((HtmlDocument.Text) node);
- } else {
- mergeTextNodes(textNodes, out);
- out.add(node);
- }
- }
- mergeTextNodes(textNodes, out);
- return out;
- }
-
- /**
- * Flushes any Text nodes in {@code textNodes} into a single Text node
- * in {@code output}. {@code textNodes} is guaranteed to be empty when
- * the function returns.
- * @param textNodes Text nodes.
- * @param output Destination to which results are added.
- */
- private static void mergeTextNodes(LinkedList<HtmlDocument.Text> textNodes,
- List<HtmlDocument.Node> output) {
- if (!textNodes.isEmpty()) {
- if (textNodes.size() == 1) {
- output.add(textNodes.removeFirst());
- } else {
- int combinedTextLen = 0;
- int combinedInputLen = 0;
- for (HtmlDocument.Text text : textNodes) {
- combinedTextLen += text.getText().length();
- if (text.getOriginalHTML() != null) {
- combinedInputLen += text.getOriginalHTML().length();
- }
- }
- StringBuilder combinedText = new StringBuilder(combinedTextLen);
- StringBuilder combinedInput = new StringBuilder(combinedInputLen);
- while (!textNodes.isEmpty()) {
- HtmlDocument.Text text = textNodes.removeFirst();
- combinedText.append(text.getText());
- if (text.getOriginalHTML() != null) {
- combinedInput.append(text.getOriginalHTML());
- }
- }
- String originalInput = combinedInputLen > 0 ? combinedInput.toString() : null;
- output.add(HtmlDocument.createText(combinedText.toString(), originalInput));
- }
- }
- }
-
- //------------------------------------------------------------------------
- // Text scanning
- //------------------------------------------------------------------------
- /**
- * A truncated entity is something like <pre>&nbs or a3</pre>.
- * We only want to find these at the end of a clipped text.
- */
- private static final Pattern TRUNCATED_ENTITY =
- Pattern.compile("\\& \\#? [0-9a-zA-Z]{0,8} $", Pattern.COMMENTS);
-
- /**
- * In a text mode, scan for a tag
- * @param start Position in original html.
- * @param end Position in original html.
- * @return End position of scanned content.
- */
- int scanText(final int start, final int end) {
- int pos;
- for (pos = start; pos < end; pos++) {
- char ch = html.charAt(pos);
- if (ch == '<' && pos + 1 < end) {
- // Check the next char
- ch = html.charAt(pos + 1);
- if (ch == '/' || Character.isLetter(ch) || ch == '!' || ch == '?') {
-
- // Check if it's an html comment or tag
- if (html.regionMatches(pos + 1, "!--", 0, 3)) {
- state = State.IN_COMMENT;
- } else {
- state = State.IN_TAG;
- }
- break;
- }
- }
- }
-
- if (pos > start) {
- int finalPos = pos;
- String htmlTail = this.html.substring(start, finalPos);
-
- if ((pos == clipLength) && (clipLength < html.length())) {
- // We're clipping this HTML, not running off the end.
- // If we're ending with what looks like a truncated entity,
- // then clip that part off, too.
- // If it really was a truncated entity, great.
- // If it was a false positive, the user won't notice that we clipped
- // an additional handful of characters.
- Matcher matcher = TRUNCATED_ENTITY.matcher(htmlTail);
- if (matcher.find()) {
- int matchStart = matcher.start();
- // The matcher matched in htmlTail, not html.
- // htmlTail starts at html[start]
- finalPos = start + matchStart;
- htmlTail = htmlTail.substring(0, matchStart);
- }
- }
-
- if (finalPos > start) {
- String originalHtml = null;
- if (preserveAll) {
- originalHtml = htmlTail;
- } else if (preserveValidHtml) {
- // the only way htmlTail can start with '<' is if it's the last character
- // in html; otherwise, we would have entered State.IN_TAG or
- // State.IN_COMMENT above
-
- // officially a '<' can be valid in a text node, but to be safe we
- // always escape them
- originalHtml = CharMatcher.is('<').replaceFrom(htmlTail, "<");
- }
-
- HtmlDocument.Text textnode = HtmlDocument.createEscapedText(htmlTail, originalHtml);
- nodes.add(textnode);
- }
- }
- return pos;
- }
-
- //------------------------------------------------------------------------
- // Tag name scanning utility class
- //------------------------------------------------------------------------
- private static class TagNameScanner {
- private final String html;
- private String tagName;
- private int startNamePos = -1;
- private int endNamePos = -1;
-
- public TagNameScanner(String html) {
- this.html = html;
- }
-
- /**
- * Scans for a tag name. Sets #startNamePos and #endNamePos.
- * @param start Position in original html.
- * @param end Position in original html.
- * @return End position of scanned content.
- */
- public int scanName(final int start, final int end) {
- int pos;
- for (pos = start; pos < end; pos++) {
- char ch = html.charAt(pos);
-
- // End of tag or end of name.
- if ((ch == '>') || (ch == '/') || Character.isWhitespace(ch)) {
- break;
- }
- }
- if (pos > start) {
- startNamePos = start;
- endNamePos = pos;
- }
- return pos;
- }
-
- /**
- * @return Tag name.
- */
- public String getTagName() {
- if (tagName == null && startNamePos != -1 && endNamePos != -1) {
- tagName = html.substring(startNamePos, endNamePos);
- }
- return tagName;
- }
- }
-
- //------------------------------------------------------------------------
- // Attribute scanning utility class
- //------------------------------------------------------------------------
- private static class AttributeScanner {
- private final String html;
- private String name;
- private String value;
-
- // The following have package visibility because they are accessed from
- // HtmlParser.addAttribute() to handle preservation of original content
- // around the attribute value, but quoting and escaping of the value itself.
- int startNamePos = -1;
- int endNamePos = -1;
- int startValuePos = -1;
- int endValuePos = -1;
- boolean attrValueIsQuoted = false;
-
- public AttributeScanner(String html) {
- this.html = html;
- }
-
- /**
- * Reset to scan another attribute.
- */
- public void reset() {
- startNamePos = -1;
- endNamePos = -1;
- startValuePos = -1;
- endValuePos = -1;
- attrValueIsQuoted = false;
- name = null;
- value = null;
- }
-
- /**
- * Scans for a tag attribute name. Sets startNamePos and endNamePos. Sets
- * 'attrName'.
- *
- * @param start Position in original html
- * @param end Position in original html
- * @return End position of scanned content
- */
- int scanName(final int start, final int end) {
- X.assertTrue(html.charAt(start) != '>');
- if (start == end) {
- // No attribute name
- return start;
- }
-
- int pos;
- for (pos = start + 1; pos < end; pos++) {
- char ch = html.charAt(pos);
-
- // End of tag or end of name.
- if ((ch == '>') || (ch == '=') || (ch == '/') || Character.isWhitespace(ch)) {
- break;
- }
- }
- startNamePos = start;
- endNamePos = pos;
- return pos;
- }
-
- /**
- * Scans for a tag attribute value. Sets startValuePos_ and endValuePos_.
- *
- * @param start Position in original html
- * @param end Position in original html
- * @return End position of scanned content
- */
- int scanValue(final int start, final int end) {
- // Skip whitespace before '='.
- int pos = skipSpaces(start, end);
-
- // Handle cases with no attribute value.
- if ((pos == end) || (html.charAt(pos) != '=')) {
- // Return start so spaces will be parsed as part of next attribute,
- // or end of tag.
- return start;
- }
-
- // Skip '=' and whitespace after it.
- pos++;
- pos = skipSpaces(pos, end);
-
- // Handle cases with no attribute value.
- if (pos == end) {
- return pos;
- }
-
- // Check for quote character ' or "
- char ch = html.charAt(pos);
- if (ch == '\'' || ch == '\"') {
- attrValueIsQuoted = true;
- pos++;
- int valueStart = pos;
- while (pos < end && html.charAt(pos) != ch) {
- pos++;
- }
- startValuePos = valueStart;
- endValuePos = pos;
- if (pos < end) {
- pos++; // Skip the ending quote char
- }
- } else {
- int valueStart = pos;
- for (; pos < end; pos++) {
- ch = html.charAt(pos);
-
- // End of tag or end of value. Not that '/' is included in the value
- // even if it is the '/>' at the end of the tag.
- if ((ch == '>') || Character.isWhitespace(ch)) {
- break;
- }
- }
- startValuePos = valueStart;
- endValuePos = pos;
- }
-
- X.assertTrue(startValuePos > -1);
- X.assertTrue(endValuePos > -1);
- X.assertTrue(startValuePos <= endValuePos);
- X.assertTrue(pos <= end);
-
- return pos;
- }
-
- /**
- * Skips white spaces.
- *
- * @param start Position in original html
- * @param end Position in original html
- * @return End position of scanned content
- */
- private int skipSpaces(final int start, final int end) {
- int pos;
- for (pos = start; pos < end; pos++) {
- if (!Character.isWhitespace(html.charAt(pos))) {
- break;
- }
- }
- return pos;
- }
-
- public String getName() {
- if (name == null && startNamePos != -1 && endNamePos != -1) {
- name = html.substring(startNamePos, endNamePos);
- }
- return name;
- }
-
- public String getValue() {
- if (value == null && startValuePos != -1 && endValuePos != -1) {
- value = html.substring(startValuePos, endValuePos);
- }
- return value;
- }
- }
-
- /**
- * Holds any unrecognized elements we encounter. Only applicable in
- * PRESERVE_ALL mode.
- */
- private final HashMap<String,HTML.Element> unknownElements = Maps.newHashMap();
-
- /**
- * Holds any unrecognized attributes we encounter. Only applicable in
- * PRESERVE_ALL mode.
- */
- private final HashMap<String,HTML.Attribute> unknownAttributes = Maps.newHashMap();
-
- /**
- * @param name Element name.
- * @return "Dummy" element. Not useful for any real HTML processing, but
- * gives us a placeholder for tracking original HTML contents.
- */
- private HTML.Element lookupUnknownElement(String name) {
- name = name.toLowerCase();
- HTML.Element result = unknownElements.get(name);
- if (result == null) {
- result = new HTML.Element(name,
- HTML.Element.NO_TYPE,
- /* empty */ false,
- /* optional end tag */ true,
- /* breaks flow*/ false,
- HTML.Element.Flow.NONE);
- unknownElements.put(name, result);
- }
- return result;
- }
-
- /**
- * @param name Attribute name.
- * @return "Dummy" attribute. Not useful for any real HTML processing, but
- * gives us a placeholder for tracking original HTML contents.
- */
- private HTML.Attribute lookupUnknownAttribute(String name) {
- name = name.toLowerCase();
- HTML.Attribute result = unknownAttributes.get(name);
- if (result == null) {
- result = new HTML.Attribute(name, HTML.Attribute.NO_TYPE);
- unknownAttributes.put(name, result);
- }
- return result;
- }
-
- /**
- * Scans for an HTML tag.
- *
- * @param start Position in original html.
- * @param end Position in original html.
- * @return End position of scanned content.
- */
- int scanTag(final int start, final int end) {
- X.assertTrue(html.charAt(start) == '<');
-
- // nameStart is where we begin scanning for the tag name and attributes,
- // so we skip '<'.
- int nameStart = start + 1;
-
- // Next state is Text, except the case when we see a STYLE/SCRIPT tag. See
- // code below.
- state = State.IN_TEXT;
-
- // End tag?
- boolean isEndTag = false;
- if (html.charAt(nameStart) == '/') {
- isEndTag = true;
- ++nameStart;
- }
-
- // Tag name and element
- TagNameScanner tagNameScanner = new TagNameScanner(html);
- int pos = tagNameScanner.scanName(nameStart, end);
- String tagName = tagNameScanner.getTagName();
- HTML.Element element = null;
- if (tagName == null) {
- // For some reason, browsers treat start and end tags differently
- // when they don't have a valid tag name - end tags are swallowed
- // (e.g., "</ >"), start tags treated as text (e.g., "< >")
- if (!isEndTag) {
- // This is not really a tag, treat the '<' as text.
- HtmlDocument.Text text = HtmlDocument.createText("<", preserveAll ? "<" : null);
- nodes.add(text);
- state = State.IN_TEXT;
- return nameStart;
- }
-
- if (preserveAll) {
- element = lookupUnknownElement("");
- }
- } else {
- element = lookupElement(tagName);
- if (element == null) {
- if (DEBUG) {
- // Unknown element
- debug("Unknown element: " + tagName);
- }
- if (preserveAll) {
- element = lookupUnknownElement(tagName);
- }
- }
- }
-
- // Attributes
- boolean isSingleTag = false;
- ArrayList<HtmlDocument.TagAttribute> attributes = null;
- int allAttributesStartPos = pos;
- int nextAttributeStartPos = pos;
- AttributeScanner attributeScanner = new AttributeScanner(html);
- while (pos < end) {
- int startPos = pos;
- char ch = html.charAt(pos);
-
- // Are we at the end of the tag?
- if ((pos + 1 < end) && (ch == '/') && (html.charAt(pos + 1) == '>')) {
- isSingleTag = true;
- ++pos;
- break; // Done
- }
- if (ch == '>') {
- break; // Done
- }
-
- // See bug 870742 (Buganizer).
- if (isEndTag && ('<' == ch)) {
- // '<' not allowed in end tag, so we finish processing this tag and
- // return to State.IN_TEXT. We mimic Safari & Firefox, which both
- // terminate the tag when it contains a '<'.
- if (element != null) {
- addEndTag(element, start, allAttributesStartPos, pos);
- }
- state = State.IN_TEXT;
- return pos;
- }
-
- if (Character.isWhitespace(ch)) {
- // White space, skip it.
- ++pos;
- } else {
- // Scan for attribute
- attributeScanner.reset();
- pos = attributeScanner.scanName(pos, end);
- X.assertTrue(pos > startPos);
-
- // If it's a valid attribute, scan attribute values
- if (attributeScanner.getName() != null) {
- pos = attributeScanner.scanValue(pos, end);
-
- // Add the attribute to the list
- if (element != null) {
- if (attributes == null) {
- attributes = new ArrayList<HtmlDocument.TagAttribute>();
- }
- addAttribute(attributes, attributeScanner, nextAttributeStartPos, pos);
- }
- nextAttributeStartPos = pos;
- }
- }
-
- // Make sure that we make progress!
- X.assertTrue(pos > startPos);
- }
-
- // Cannot find the close tag, so we treat this as text
- if (pos == end) {
- X.assertTrue(start < end);
- String textNodeContent = html.substring(start, end);
- String originalContent = null;
- if (preserveAll) {
- originalContent = textNodeContent;
- } else if (preserveValidHtml) {
- // Officially a '<' can be valid in a text node, but to be safe we
- // always escape them.
- originalContent =
- CharMatcher.is('<').replaceFrom(html.substring(start, end), "<");
- }
- nodes.add(HtmlDocument.createEscapedText(textNodeContent, originalContent));
- return end;
- }
-
- // Skip '>'
- X.assertTrue(html.charAt(pos) == '>');
- pos++;
-
- // Check if it's an element we're keeping (either an HTML4 element, or an
- // unknown element we're preserving). If not, ignore the tag.
- if (element != null) {
- if (isEndTag) {
- addEndTag(element, start, allAttributesStartPos, pos);
- } else {
- // Special case: if it's a STYLE/SCRIPT element, we go to into
- // CDATA state.
- if (HTML4.SCRIPT_ELEMENT.equals(element) || HTML4.STYLE_ELEMENT.equals(element)) {
- state = State.IN_CDATA;
- }
-
- addStartTag(element, start, allAttributesStartPos,
- nextAttributeStartPos,
- pos, isSingleTag, attributes);
- }
- }
-
- return pos;
- }
-
- /**
- * Lookups the element in our whitelist(s). Whitelists are consulted in
- * reverse chronological order (starting from the most recently added
- * whitelist), allowing clients to override the default behavior.
- *
- * @param name Element name.
- * @return Element.
- */
- HTML.Element lookupElement(String name) {
- ListIterator<HtmlWhitelist> iter = whitelists.listIterator(whitelists.size());
- while (iter.hasPrevious()) {
- HTML.Element elem = iter.previous().lookupElement(name);
- if (elem != null) {
- return elem;
- }
- }
- return null;
- }
-
- /**
- * Lookups the attribute in our whitelist(s). Whitelists are consulted in
- * reverse chronological order (starting from the most recently added
- * whitelist), allowing clients to override the default behavior.
- *
- * @param name Attribute name.
- * @return Attribute.
- */
- HTML.Attribute lookupAttribute(String name) {
- ListIterator<HtmlWhitelist> iter = whitelists.listIterator(whitelists.size());
- while (iter.hasPrevious()) {
- HTML.Attribute attr = iter.previous().lookupAttribute(name);
- if (attr != null) {
- return attr;
- }
- }
- return null;
- }
-
- /**
- * @param element Tag element
- * @param startPos Start of tag, including '<'
- * @param startAttributesPos Start of attributes. This is the first
- * character after the tag name. If there are no attributes, this is the end
- * of the tag.
- * @param endAttributesPos First position after last attribute
- * @param endPos End of tag, including '>' character
- * @param isSingleTag True iff this is a self-terminating tag
- * @param attributes Tag attributes
- */
- private void addStartTag(HTML.Element element, final int startPos,
- final int startAttributesPos, final int endAttributesPos,
- final int endPos, final boolean isSingleTag,
- ArrayList<HtmlDocument.TagAttribute> attributes) {
- X.assertTrue(startPos < startAttributesPos);
- X.assertTrue(startAttributesPos <= endAttributesPos);
- X.assertTrue(endAttributesPos <= endPos);
-
- if (preserveAll) {
- String beforeAttrs = html.substring(startPos, startAttributesPos);
- String afterAttrs = html.substring(endAttributesPos, endPos);
- HtmlDocument.Tag tag = (isSingleTag)
- ? HtmlDocument.createSelfTerminatingTag(element, attributes,
- beforeAttrs, afterAttrs)
- : HtmlDocument.createTag(element, attributes,
- beforeAttrs, afterAttrs);
- nodes.add(tag);
- } else if (preserveValidHtml) {
- // This is the beginning of the tag up through the tag name. It should not
- // be possible for this to contain characters needing escaping, but we add
- // this redundant check to avoid an XSS attack that might get past our
- // parser but trick a browser into executing a script.
- X.assertTrue(html.charAt(startPos) == '<');
- StringBuilder beforeAttrs = new StringBuilder("<");
- String tagName = html.substring(startPos + 1, startAttributesPos);
- beforeAttrs.append(CharEscapers.asciiHtmlEscaper().escape(tagName));
-
- // Verify end-of-tag characters
- int endContentPos = endPos - 1;
- X.assertTrue(html.charAt(endContentPos) == '>');
- if (isSingleTag) {
- --endContentPos;
- X.assertTrue(html.charAt(endContentPos) == '/');
- }
- X.assertTrue(endAttributesPos <= endContentPos);
-
- // This is any extra characters between the last attribute and the end of
- // the tag.
- X.assertTrue(endAttributesPos < endPos);
- String afterAttrs = html.substring(endAttributesPos, endPos);
-
- // Strip all but preceding whitespace.
- HtmlDocument.Tag tag = (isSingleTag)
- ? HtmlDocument.createSelfTerminatingTag(element, attributes,
- beforeAttrs.toString(), afterAttrs)
- : HtmlDocument.createTag(element, attributes,
- beforeAttrs.toString(), afterAttrs);
- nodes.add(tag);
- } else {
- // Normalize.
- HtmlDocument.Tag tag = (isSingleTag)
- ? HtmlDocument.createSelfTerminatingTag(element, attributes)
- : HtmlDocument.createTag(element, attributes);
- nodes.add(tag);
- }
- }
-
- /**
- * @param element End tag element.
- * @param startPos Start of tag, including '<'.
- * @param startAttributesPos Start of attributes. This is the first
- * character after the tag name. If there are no attributes, this is the end
- * of the tag.
- * @param endPos End of tag. This usually contains the '>' character, but in
- * the case where browsers force a termination of a malformed tag, it doesn't.
- */
- private void addEndTag(HTML.Element element, final int startPos,
- final int startAttributesPos, final int endPos) {
- X.assertTrue(element != null);
- X.assertTrue(html.charAt(startPos) == '<');
- X.assertTrue(html.charAt(startPos + 1) == '/');
-
- if (preserveAll) {
- // Preserve all: keep actual content even if it's malformed.
- X.assertTrue(startPos < endPos);
- String content = html.substring(startPos, endPos);
- nodes.add(HtmlDocument.createEndTag(element, content));
- } else if (preserveValidHtml) {
- // Preserve valid: terminate the tag.
-
- StringBuilder validContent = new StringBuilder("</");
-
- // This is the beginning of the tag up through the tag name. It should not
- // be possible for this to contain characters needing escaping, but we add
- // this redundant check to avoid an XSS attack that might get past our
- // parser but trick a browser into executing a script.
- X.assertTrue(startPos < startAttributesPos);
- String tagName = html.substring(startPos + 2, startAttributesPos);
- validContent.append(CharEscapers.asciiHtmlEscaper().escape(tagName));
-
- // This is the rest of the tag, including any attributes.
- // See bug 874396 (Buganizer). We don't allow attributes in an end tag.
- X.assertTrue(startAttributesPos <= endPos);
- String endOfTag = html.substring(startAttributesPos, endPos);
- if (endOfTag.charAt(endOfTag.length() - 1) != '>') {
- endOfTag += '>';
- }
-
- // Strip everything but leading whitespace.
- validContent.append(endOfTag.replaceAll("\\S+.*>", ">"));
-
- nodes.add(HtmlDocument.createEndTag(element, validContent.toString()));
- } else {
- // Normalize: ignore the original content.
- nodes.add(HtmlDocument.createEndTag(element));
- }
- }
-
- /**
- * Creates and adds an attribute to the list.
- *
- * @param attributes Destination of new attribute.
- * @param scanner Scanned attribute.
- * @param startPos start position (inclusive) in original HTML of this
- * attribute, including preceeding separator characters (generally this
- * is whitespace, but it might contain other characters). This is the
- * end position of the tag name or previous attribute +1.
- * @param endPos end position (exclusive) in original HTML of this attribute.
- */
- private void addAttribute(ArrayList<HtmlDocument.TagAttribute> attributes,
- AttributeScanner scanner, final int startPos, final int endPos) {
- X.assertTrue(startPos < endPos);
-
- String name = scanner.getName();
- X.assertTrue(name != null);
- HTML.Attribute htmlAttribute = lookupAttribute(name);
-
- // This can be null when there's no value, e.g., input.checked attribute.
- String value = scanner.getValue();
-
- if (htmlAttribute == null) {
- // Unknown attribute.
- if (DEBUG) {
- debug("Unknown attribute: " + name);
- }
- if (preserveAll) {
- String original = html.substring(startPos, endPos);
- attributes.add(HtmlDocument.createTagAttribute(
- lookupUnknownAttribute(name), value, original));
- }
- } else {
- String unescapedValue = (value == null) ? null : StringUtil.unescapeHTML(value);
- if (preserveAll) {
- attributes.add(HtmlDocument.createTagAttribute(htmlAttribute,
- unescapedValue, html.substring(startPos, endPos)));
- } else if (preserveValidHtml) {
- StringBuilder original = new StringBuilder();
-
- // This includes any separator characters between the tag name or
- // preceding attribute and this one.
- // This addresses bugs 870757 and 875303 (Buganizer).
- // Don't allow non-whitespace separators between attributes.
- X.assertTrue(startPos <= scanner.startNamePos);
- String originalPrefix = html.substring(
- startPos, scanner.startNamePos).replaceAll("\\S+", "");
- if (originalPrefix.length() == 0) {
- originalPrefix = " ";
- }
- original.append(originalPrefix);
-
- if (value == null) {
- // This includes the name and any following whitespace. Escape in case
- // the name has any quotes or '<' that could confuse a browser.
- X.assertTrue(scanner.startNamePos < endPos);
- String nameEtc = html.substring(scanner.startNamePos, endPos);
- original.append(CharEscapers.asciiHtmlEscaper().escape(nameEtc));
- } else {
- // Escape name in case the name has any quotes or '<' that could
- // confuse a browser.
- original.append(CharEscapers.asciiHtmlEscaper().escape(name));
-
- // This includes the equal sign, and any other whitespace
- // between the name and value. It also contains the opening quote
- // character if there is one.
- X.assertTrue(scanner.endNamePos < scanner.startValuePos);
- original.append(html.substring(scanner.endNamePos, scanner.startValuePos));
-
- // This is the value, excluding any quotes.
- if (scanner.attrValueIsQuoted) {
- // Officially a '<' can be valid in an attribute value, but to be
- // safe we always escape them.
- original.append(value.replaceAll("<", "<"));
- } else {
- // This addresses bug 881426 (Buganizer). Put quotes around any
- // dangerous characters, which is what most of the browsers do.
- if (NEEDS_QUOTING_ATTRIBUTE_VALUE_REGEX.matcher(value).find()) {
- original.append('"');
- original.append(value.replaceAll("\"", """));
- original.append('"');
- } else {
- original.append(value);
- }
- }
-
- // This includes end quote, if applicable.
- X.assertTrue(scanner.endValuePos <= endPos);
- original.append(html.substring(scanner.endValuePos, endPos));
- }
-
- attributes.add(HtmlDocument.createTagAttribute(
- htmlAttribute, unescapedValue, original.toString()));
- } else {
- attributes.add(HtmlDocument.createTagAttribute(
- htmlAttribute, unescapedValue));
- }
- }
- }
-
- //------------------------------------------------------------------------
- // Comment scanning
- //------------------------------------------------------------------------
- private static final String START_COMMENT = "<!--";
- private static final String END_COMMENT = "-->";
-
- private int scanComment(final int start, final int end) {
-
- X.assertTrue(html.regionMatches(start, START_COMMENT, 0, START_COMMENT.length()));
-
- // Scan for end of comment
- int pos = html.indexOf(END_COMMENT, start + START_COMMENT.length());
- if (pos != -1) {
- pos += END_COMMENT.length();
- } else {
- // Look for '>'. If we can't find that, the rest of the text is comments.
- pos = html.indexOf('>', start + 4);
- if (pos != -1) {
- ++pos;
- } else {
- pos = end;
- }
- }
-
- if (preserveAll) {
- nodes.add(HtmlDocument.createHtmlComment(html.substring(start, pos)));
- }
-
- return pos;
- }
-
- //------------------------------------------------------------------------
- // CDATA scanning
- //------------------------------------------------------------------------
- int scanCDATA(final int start, final int end) {
-
- // Get the tag: must be either STYLE or SCRIPT
- HtmlDocument.Tag tag = (HtmlDocument.Tag) nodes.get(nodes.size() - 1);
- HTML.Element element = tag.getElement();
- X.assertTrue(HTML4.SCRIPT_ELEMENT.equals(element) || HTML4.STYLE_ELEMENT.equals(element));
-
- int pos;
- for (pos = start; pos < end; pos++) {
- if (pos + 2 < end &&
- html.charAt(pos) == '<' &&
- html.charAt(pos + 1) == '/' &&
- html.regionMatches(true, pos + 2, element.getName(), 0,
- element.getName().length())) {
- break;
- }
- }
-
- // Add a CDATA node
- if (pos > start) {
- HtmlDocument.CDATA cdata =
- HtmlDocument.createCDATA(html.substring(start, pos));
- nodes.add(cdata);
- }
-
- state = State.IN_TAG;
- return pos;
- }
-
- //------------------------------------------------------------------------
- public static void main(String[] args) throws IOException {
-
- DEBUG = true;
-
- String html = new String(ByteStreams.toByteArray(System.in), "ISO-8859-1");
-
- HtmlParser parser = new HtmlParser();
- HtmlDocument doc = parser.parse(html);
- System.out.println(doc.toString());
- }
-
- private static void debug(String str) {
- System.err.println(str);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/html/parser/HtmlTree.java b/src/com/android/mail/lib/html/parser/HtmlTree.java
deleted file mode 100644
index e64a8d5..0000000
--- a/src/com/android/mail/lib/html/parser/HtmlTree.java
+++ /dev/null
@@ -1,965 +0,0 @@
-/**
- * Copyright (c) 2004, 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.mail.lib.html.parser;
-
-import com.android.mail.lib.base.CharMatcher;
-import com.android.mail.lib.base.Preconditions;
-import com.android.mail.lib.base.X;
-import com.google.common.collect.ImmutableSet;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.Stack;
-import java.util.logging.Logger;
-
-/**
- * HtmlTree represents a parsed and well-formed html text, it provides
- * methods to convert to plain text. It also provides methods to find
- * well-formed blocks of text, for quote detection.
- *
- * We don't really build a html tree data structure. Instead, for
- * efficiency, and for the ability to do a simple in-order-traversal
- * of the tree, we simply keeps a linear list of nodes (in-order).
- * The begin_ and end_ arrays keeps track of the starting end ending
- * nodes:
- *
- * For a string node, begin_[node] = end_[node] = node
- * For an open tag, begin_[node] = node, end_[node] = the matching end tag
- * For a close tag, end_[node] = the matching open tag, end_[node] = node
- *
- * @author jlim@google.com (Jing Yee Lim)
- */
-public class HtmlTree {
-
- /**
- * An interface that allows clients to provide their own implementation
- * of a {@link PlainTextConverter}.
- */
- public static interface PlainTextConverterFactory {
- /**
- * Creates a new instance of a {@link PlainTextConverter} to convert
- * the contents of an {@link HtmlTree} to plain text.
- */
- PlainTextConverter createInstance();
- }
-
- /**
- * An interface for an object which converts a single HtmlTree into
- * plaintext.
- */
- public static interface PlainTextConverter {
- /**
- * Adds the given node {@code n} to plain text.
- *
- * @param n The node to convert to text.
- * @param nodeNum The number of the node among the list of all notes.
- * @param endNum The number of the ending node if this is a start node,
- * otherwise the same as {@code nodeNum}.
- */
- void addNode(HtmlDocument.Node n, int nodeNum, int endNum);
-
- /**
- * Returns the current length of the plain text.
- */
- int getPlainTextLength();
-
- /**
- * Returns the current plain text.
- */
- String getPlainText();
- }
-
- /** A factory that produces converters of the default implementation. */
- private static final PlainTextConverterFactory DEFAULT_CONVERTER_FACTORY =
- new PlainTextConverterFactory() {
- public PlainTextConverter createInstance() {
- return new DefaultPlainTextConverter();
- }
- };
-
- /** Contains html nodes */
- private final List<HtmlDocument.Node> nodes = new ArrayList<HtmlDocument.Node>();
-
- /** Keeps track of beginning and end of each node */
- private final Stack<Integer> begins = new Stack<Integer>();
- private final Stack<Integer> ends = new Stack<Integer>();
-
- /** Plain text (lazy creation) */
- private String plainText;
-
- /** The html string (lazy creation) */
- private String html;
-
- /** textPositions[node pos] = the text position */
- private int[] textPositions;
-
- private PlainTextConverterFactory converterFactory = DEFAULT_CONVERTER_FACTORY;
-
- // For debugging only
- private static final boolean DEBUG = false;
-
- private static final Logger logger = Logger.getLogger(HtmlTree.class.getName());
-
- //------------------------------------------------------------------------
-
- /** HtmlTree can only be constructed from this package */
- HtmlTree() {
- }
-
- /**
- * Sets a new {@link PlainTextConverterFactory} to be used to convert
- * the contents of this tree to plaintext.
- */
- public void setPlainTextConverterFactory(PlainTextConverterFactory factory) {
- if (factory == null) {
- throw new NullPointerException("factory must not be null");
- }
- converterFactory = factory;
- }
-
- /**
- * Gets the list of node objects. A node can be either a
- * Tag, EngTag or a String object.
- * @return the nodes of the tree
- */
- public List<HtmlDocument.Node> getNodesList() {
- return Collections.unmodifiableList(nodes);
- }
-
- /**
- * @return number of nodes
- */
- public int getNumNodes() {
- return nodes.size();
- }
-
- /**
- * Gets the entire html.
- */
- public String getHtml() {
- return getHtml(-1);
- }
-
- /**
- * Gets the entire html, if wrapSize is > 0, it tries to do wrapping at the
- * specified size.
- */
- public String getHtml(int wrapSize) {
- if (html == null) {
- html = getHtml(0, nodes.size(), wrapSize);
- }
- return html;
- }
-
- /** Gets parts of the html */
- public String getHtml(int fromNode, int toNode) {
- return getHtml(fromNode, toNode, -1);
- }
-
- /**
- * Gets parts of the html, if wrapSize is > 0, it tries
- * to do wrapping at the specified size.
- */
- public String getHtml(int fromNode, int toNode, int wrapSize) {
- X.assertTrue(fromNode >= 0 && toNode <= nodes.size());
-
- int estSize = (toNode - fromNode) * 10;
- StringBuilder sb = new StringBuilder(estSize);
- int lastWrapIndex = 0; // used for wrapping
- for (int n = fromNode; n < toNode; n++) {
- HtmlDocument.Node node = nodes.get(n);
- node.toHTML(sb);
- // TODO: maybe we can be smarter about this and not add newlines
- // within <pre> tags, unless the whole long line is encompassed
- // by the <pre> tag.
- if (wrapSize > 0) {
- // We can only wrap if the last outputted node is an element that
- // breaks the flow. Otherwise, we risk the possibility of inserting
- // spaces where they shouldn't be.
- if ((node instanceof HtmlDocument.Tag &&
- ((HtmlDocument.Tag) node).getElement().breaksFlow()) ||
- (node instanceof HtmlDocument.EndTag &&
- ((HtmlDocument.EndTag) node).getElement().breaksFlow())) {
- // Check to see if there is a newline in the most recent node's html.
- int recentNewLine = sb.substring(lastWrapIndex + 1).lastIndexOf('\n');
- if (recentNewLine != -1) {
- lastWrapIndex += recentNewLine;
- }
- // If the last index - last index of a newline is greater than
- // wrapSize, add a newline.
- if (((sb.length() - 1) - lastWrapIndex) > wrapSize) {
- sb.append('\n');
- lastWrapIndex = sb.length() - 1;
- }
- }
- }
- }
-
- return sb.toString();
- }
-
- /**
- * Convert a html region into chunks of html code, each containing
- * roughly chunkSize characters.
- */
- public ArrayList<String> getHtmlChunks(int fromNode, int toNode, int chunkSize) {
- X.assertTrue(fromNode >= 0 && toNode <= nodes.size());
-
- ArrayList<String> chunks = new ArrayList<String>();
-
- // Do a best effort attempt to not split apart certain elements (as of now,
- // just the <textarea>). We cannot guarantee that they will not be split
- // because the client may specify endpoint nodes that land in the middle
- // of an element (although this shouldn't happen if the endpoints returned
- // by createBlocks() are properly used).
- int stack = 0;
- boolean balanced = true;
-
- StringBuilder sb = new StringBuilder(chunkSize + 256);
- for (int n = fromNode; n < toNode; n++) {
- HtmlDocument.Node node = nodes.get(n);
- node.toHTML(sb);
-
- if (node instanceof HtmlDocument.Tag) {
- if (HTML4.TEXTAREA_ELEMENT.equals(
- ((HtmlDocument.Tag)node).getElement())) {
- stack++;
- }
- }
- if (node instanceof HtmlDocument.EndTag) {
- if (HTML4.TEXTAREA_ELEMENT.equals(
- ((HtmlDocument.EndTag)node).getElement())) {
- if (stack == 0) {
- balanced = false;
- } else {
- stack--;
- }
- }
- }
-
- if (stack == 0 && sb.length() >= chunkSize) {
- chunks.add(sb.toString());
- sb.setLength(0);
- }
- }
-
- // Don't forget the last chunk!
- if (sb.length() > 0) {
- chunks.add(sb.toString());
- }
-
- // If the tree is not balanced (cut off in the middle of a node), log
- // debug data. Clients should fix their code so that the endpoints from
- // createBlocks() are properly used.
- if (!balanced || stack != 0) {
- StringBuilder debug = new StringBuilder("Returning unbalanced HTML:\n");
- debug.append(getHtml());
- debug.append("\nfromNode: ").append(fromNode);
- debug.append("\ntoNode: ").append(toNode);
- debug.append("\nNum nodes_: ").append(getNumNodes());
- for (String chunk : chunks) {
- debug.append("\nChunk:\n").append(chunk);
- }
- logger.severe(debug.toString());
- }
-
- return chunks;
- }
-
- /**
- * Returns height (maximum length from root to a leaf) of the HTML tree.
- * @return height of the HTML tree.
- */
- public int getTreeHeight() {
- int currentHeight = 0;
- int maxHeight = 0;
-
- for (int i = 0; i < nodes.size(); i++) {
- HtmlDocument.Node node = nodes.get(i);
- if (node instanceof HtmlDocument.Tag) {
- currentHeight++;
- if (currentHeight > maxHeight) {
- maxHeight = currentHeight;
- }
- if (((HtmlDocument.Tag) node).getElement().isEmpty()) {
- // Empty tags have no closing pair, so decrease counter here.
- currentHeight--;
- }
- } else if (node instanceof HtmlDocument.EndTag) {
- currentHeight--;
- }
- }
-
- // TODO(anatol): make this value cachable?
- return maxHeight;
- }
-
- //------------------------------------------------------------------------
- // Creating well-formed blocks within the html tree.
- //------------------------------------------------------------------------
- /**
- * A Block represents a region of a html tree that
- * 1) is well-formed, i.e. for each node in the block, all its descendants
- * are also contained in the block. So it's safe to wrap the region
- * within a <table> or <div>, etc.
- * 2) starts at the beginning of a "line", e.g. a <div>, a <br>.
- */
- public static class Block {
- /* The starting node */
- public int start_node;
-
- /* The ending node (non-inclusive to the block) */
- public int end_node;
- }
-
- /**
- * Creates a list of Blocks, given a text-range.
- * We may create multiple blocks if one single well-formed Block cannot be
- * created.
- *
- * @param textStart beginning plain-text offset
- * @param textEnd beginning plain-text offset
- * @param minNode the smallest node number
- * @param maxNode the largest node number
- * @return a list of 0 or more Block objects, never null
- */
- public ArrayList<Block> createBlocks(int textStart, int textEnd, int minNode, int maxNode) {
-
- ArrayList<Block> blocks = new ArrayList<Block>();
- int startNode = Math.max(getBlockStart(textStart), minNode);
- int endNode = Math.min(getBlockEnd(textEnd), maxNode);
-
- if (DEBUG) {
- debug("Creating block: " +
- "text pos: " + textStart + "-" + textEnd + "\n" +
- "node pos: " + startNode + "-" + endNode + "\n" +
- plainText.substring(textStart, textEnd));
- }
-
- // Split up the block [start, end) into one or more blocks that
- // are well-formed, and begins at a "line" boundary.
- int blockStart = -1;
- for (int n = startNode; n < endNode;) {
-
- // The node n spans [nBegin, nEnd]
- int nBegin = begins.get(n);
- int nEnd = ends.get(n);
-
- if (blockStart == -1) {
- // Check if this is a valid start node
- if (nBegin >= n && nEnd <= endNode &&
- canBeginBlockAt(n)) {
- blockStart = n;
- n = nEnd + 1;
- } else {
- n++;
- }
- continue;
- }
-
- // If the node [nBegin, nEnd) lies completely within
- // the region then proceed to the (nEnd + 1).
- if (nBegin >= blockStart && nEnd < endNode) {
- n = nEnd + 1;
- continue;
- }
-
- // If we got here, we have to break up the region into one
- // or more blocks because the current node cannot be included
- // in the region.
- if (DEBUG) {
- debug("Forcing new block: " + n + " (" + nBegin + " " + nEnd +
- ") exceeds (" + blockStart + " " + endNode + ")");
- }
- Block b = new Block();
- b.start_node = blockStart;
- b.end_node = n;
- blocks.add(b);
-
- blockStart = -1;
- n++;
- }
-
- // Last block
- if (blockStart != -1) {
- Block b = new Block();
- b.start_node = blockStart;
- b.end_node = endNode;
- blocks.add(b);
- }
-
- if (DEBUG) {
- for (int i = 0; i < blocks.size(); i++) {
- Block b = blocks.get(i);
- debug("Block " + i + "/" + blocks.size() + ": " +
- b.start_node + "-" + b.end_node + " " +
- getPlainText(b.start_node, b.end_node));
- }
- }
-
- return blocks;
- }
-
- /**
- * Checks if a block can begin starting from a node position
- */
- private boolean canBeginBlockAt(int nodePos) {
- int textPos = textPositions[nodePos];
-
- // Make sure that we don't exceed the text position, this happens
- // for the last tag nodes.
- if (textPos == plainText.length()) {
- textPos--;
- }
-
- // Scan backwards to check if a nodePos is at the beginning
- // of a line.
- for (int i = textPos; i > 0; i--) {
- char ch = plainText.charAt(i);
- if (ch == '\n') {
- return true;
- }
- if (i < textPos && !Character.isWhitespace(ch)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns the start of a block given a text-pos
- */
- private int getBlockStart(int textPos) {
- int nodenum = Arrays.binarySearch(textPositions, textPos);
- if (nodenum >= 0) {
- // Got an exact node alignment. Get the outer most pos that
- // matches the text position
- while ((nodenum - 1) >= 0 && textPositions[nodenum - 1] == textPos) {
- nodenum--;
- }
- } else {
- // textPos matches the middle of a node.
- nodenum = -nodenum - 1;
- }
-
- X.assertTrue(nodenum >= 0 && nodenum <= nodes.size());
- return nodenum;
- }
-
- /**
- * Returns the end of a block given a text-pos
- */
- private int getBlockEnd(int textPos) {
- int nodenum = Arrays.binarySearch(textPositions, textPos);
- if (nodenum >= 0) {
- // Got an exact node alignment.
- while ((nodenum + 1) < textPositions.length && textPositions[nodenum + 1] == textPos) {
- nodenum++;
- }
- } else {
- // textPos matches the middle of a node.
- nodenum = -nodenum - 2;
- }
- X.assertTrue(nodenum >= 0 && nodenum <= nodes.size());
- return nodenum;
- }
-
- //------------------------------------------------------------------------
- // Plain text view of the html tree
- //------------------------------------------------------------------------
- /**
- * @return the plain-text position corresponding to the node
- */
- public int getTextPosition(int node) {
- return textPositions[node];
- }
-
- /**
- * @return a plain-text String of the html tree
- */
- public String getPlainText() {
- if (plainText == null) {
- convertToPlainText();
- }
- return plainText;
- }
-
- /**
- * @return a plain-text String of a part of the html tree
- */
- public String getPlainText(int fromNode, int toNode) {
- if (plainText == null) {
- convertToPlainText();
- }
- int textstart = textPositions[fromNode];
- int textend = textPositions[toNode];
- return plainText.substring(textstart, textend);
- }
-
- /**
- * Converts the html tree to plain text.
- * We simply iterate through the nodes in the tree.
- * As we output the plain-text, we keep track of the text position
- * of each node.
- * For String nodes, we replace '\n' with ' ' unless we're in a
- * <pre> block.
- */
- private void convertToPlainText() {
- X.assertTrue(plainText == null && textPositions == null);
-
- int numNodes = nodes.size();
-
- // Keeps track of start text position of each node, including a last
- // entry for the size of the text.
- textPositions = new int[numNodes + 1];
-
- PlainTextConverter converter = converterFactory.createInstance();
-
- for (int i = 0; i < numNodes; i++) {
- textPositions[i] = converter.getPlainTextLength();
- converter.addNode(nodes.get(i), i, ends.get(i));
- }
-
- // Add a last entry, so that textPositions_[nodes_.size()] is valid.
- textPositions[numNodes] = converter.getPlainTextLength();
-
- plainText = converter.getPlainText();
-
- if (DEBUG) {
- debug("Plain text: " + plainText);
-
- for (int i = 0; i < nodes.size(); i++) {
- int textPos = textPositions[i];
- String text = plainText.substring(textPos, textPositions[i + 1]);
- debug("At " + i + ": pos=" + textPos + " " + text);
- }
- }
- }
-
- /**
- * Encapsulates the logic for outputting plain text with respect to text
- * segments, white space separators, line breaks, and quote marks.
- */
- static final class PlainTextPrinter {
- /**
- * Separators are whitespace inserted between segments of text. The
- * semantics are such that between any two segments of text, there is
- * at most one separator. As such, separators are ordered in increasing
- * priority, and setting a separator multiple times between text will
- * result in the single separator with the highest priority being used.
- * For example, a LineBreak (one newline) will override a Space, but will
- * be overriden by a BlankLine (two newlines).
- */
- static enum Separator {
- // The values here must be ordered by increasing priority, as the
- // enum's ordinal() method is used when determining if a new separator
- // should override an existing one.
- None,
- Space, // single space
- LineBreak, // single new line
- BlankLine // two new lines
- }
-
- // White space characters that are collapsed as a single space.
- // Note that characters such as the non-breaking whitespace
- // and full-width spaces are not equivalent to the normal spaces.
- private static final String HTML_SPACE_EQUIVALENTS = " \n\r\t\f";
-
- /**
- * Determines if the given character is considered an HTML space character.
- * Consecutive HTML space characters are collapsed into a single space when
- * not within a PRE element.
- */
- private static boolean isHtmlWhiteSpace(char ch) {
- return HTML_SPACE_EQUIVALENTS.indexOf(ch) >= 0;
- }
-
- // The buffer in which we accumulate the converted plain text
- private final StringBuilder sb = new StringBuilder();
-
- // How many <blockquote> blocks we are in.
- private int quoteDepth = 0;
-
- // How many logical newlines are at the end of the buffer we've outputted.
- // Note that we can't simply count the newlines at the end of the output
- // buffer because a logical new line may be followed by quote marks.
- //
- // We initialize the value to 2 so that we consume any initial separators,
- // since we don't need separators at the beginning of the output. This also
- // results in correctly outputting any quote marks at the beginning of the
- // output if the first piece of text is within a BLOCKQUOTE element.
- private int endingNewLines = 2;
-
- // The next separator to be inserted between two text nodes.
- private Separator separator = Separator.None;
-
- /** Returns the current length of the text. */
- final int getTextLength() {
- return sb.length();
- }
-
- /** Returns the current text. */
- final String getText() {
- return sb.toString();
- }
-
- /**
- * Sets the next separator between two text nodes. A Space separator is
- * used if there is any whitespace between the two text nodes when there is
- * no intervening element that breaks flow. This is automatically handled
- * by the {@link #appendNormalText} function so the client never needs to
- * specify this separator.
- * <p>
- * A LineBreak separator (single new line) is used if text segments are
- * separated or enclosed by elements that break flow (e.g. DIV, TABLE, HR,
- * etc.). The client should set this separator for opening and closing tags
- * of any element that breaks flow.
- * <p>
- * A BlankLine separator (two new lines) should be set for opening and
- * closing P tags.
- * <p>
- * If this method is called multiple times between text nodes, a
- * separator with a higher priority will override that of a lower priority.
- */
- final void setSeparator(Separator newSeparator) {
- if (newSeparator.ordinal() > separator.ordinal()) {
- separator = newSeparator;
- }
- }
-
- /** Increments the current quote depth of the text. */
- final void incQuoteDepth() {
- quoteDepth++;
- }
-
- /** Decrements the current quote depth of the text. */
- final void decQuoteDepth() {
- quoteDepth = Math.max(0, quoteDepth - 1);
- }
-
- /**
- * Normalizes the HTML whitespace in the given {@code text} and appends it
- * as the next segment of text. This will flush any separator that should
- * be appended before the text, as well as any quote marks that should
- * follow the last newline if the quote depth is non-zero.
- */
- final void appendNormalText(String text) {
- if (text.length() == 0) {
- return;
- }
- boolean startsWithSpace = isHtmlWhiteSpace(text.charAt(0));
- boolean endsWithSpace = isHtmlWhiteSpace(text.charAt(text.length() - 1));
-
- // Strip beginning and ending whitespace.
- text = CharMatcher.anyOf(HTML_SPACE_EQUIVALENTS).trimFrom(text);
-
- // Collapse whitespace within the text.
- text = CharMatcher.anyOf(HTML_SPACE_EQUIVALENTS).collapseFrom(text, ' ');
-
- if (startsWithSpace) {
- setSeparator(Separator.Space);
- }
-
- appendTextDirect(text);
-
- if (endsWithSpace) {
- setSeparator(Separator.Space);
- }
- }
-
- /**
- * Appends the given text, preserving all whitespace. This is used for
- * appending text in a PRE element.
- */
- final void appendPreText(String text) {
- // We're in a <pre> block. Split the text into lines, and append
- // each line with appendTextDirect() to preserve white space.
- String[] lines = text.split("[\\r\\n]", -1);
-
- // split() will always return an array with at least one element.
- appendTextDirect(lines[0]);
-
- // For all of the remaining lines, we append a newline first, which
- // takes care of any quote marks that we need to output if the quote
- // depth is non-zero.
- for (int i = 1; i < lines.length; i++) {
- appendNewLine();
- appendTextDirect(lines[i]);
- }
- }
-
- /**
- * Appends the {@code text} directly to the output, taking into account
- * any separator that should be appended before it, and any quote marks
- * that should follow the last newline if the quote depth is non-zero.
- * <p>
- * {@code text} must not contain any new lines--in order to handle
- * quoting correctly, it is up to the caller to either normalize away the
- * newlines, or split the text up into separate lines and handle new lines
- * with the {@link #appendNewLine} method.
- * <p>
- * The original {@code text} is not modified in any way. Use this method
- * when you need to preserve the original white space.
- * <p>
- * If the given {@code text} is non empty, this method will result in
- * {@code endingNewLines} being reset to 0.
- */
- private void appendTextDirect(String text) {
- if (text.length() == 0) {
- return;
- }
- Preconditions.checkArgument(text.indexOf('\n') < 0,
- "text must not contain newlines.");
- flushSeparator();
- maybeAddQuoteMarks(true);
- sb.append(text);
- endingNewLines = 0;
- }
-
- /**
- * Appends a forced line break, which is the equivalent of a BR element.
- */
- final void appendForcedLineBreak() {
- flushSeparator();
- appendNewLine();
- }
-
- /**
- * Appends any pending separator to the output buffer. This should be
- * called before appending text to the buffer.
- */
- private void flushSeparator() {
- switch (separator) {
- case Space:
- if (endingNewLines == 0) {
- // Only append a space separator if we are not following a new
- // line character. For example, we don't append a separator
- // space after a <br> tag, since the <br>'s newline fulfills the
- // space separation requirement.
- sb.append(" ");
- }
- break;
- case LineBreak:
- while (endingNewLines < 1) {
- appendNewLine();
- }
- break;
- case BlankLine:
- while (endingNewLines < 2) {
- appendNewLine();
- }
- break;
- }
- separator = Separator.None;
- }
-
- /**
- * Adds a newline to the output. This handles any quote marks that should
- * follow any previous new lines, and increments {@code endingNewLines}.
- */
- private void appendNewLine() {
- maybeAddQuoteMarks(false);
- sb.append('\n');
- endingNewLines++;
- }
-
- /**
- * Adds quote marks to the output if we are at the beginning of a line.
- * One '>' character is used for every level of quoting we are in.
- *
- * @param includeEndingSpace Includes a single space after the quote marks.
- */
- private void maybeAddQuoteMarks(boolean includeEndingSpace) {
- // We only need to add quote marks if we are at the beginning of line.
- if (endingNewLines > 0 && quoteDepth > 0) {
- for (int i = 0; i < quoteDepth; i++) {
- sb.append('>');
- }
- if (includeEndingSpace) {
- sb.append(' ');
- }
- }
- }
- }
-
- /**
- * Contains the logic for converting the contents of one HtmlTree into
- * plaintext.
- */
- public static class DefaultPlainTextConverter implements PlainTextConverter {
-
- private static final Set<HTML.Element> BLANK_LINE_ELEMENTS =
- ImmutableSet.of(
- HTML4.P_ELEMENT,
- HTML4.BLOCKQUOTE_ELEMENT,
- HTML4.PRE_ELEMENT);
-
- private final PlainTextPrinter printer = new PlainTextPrinter();
-
- private int preDepth = 0;
-
- public void addNode(HtmlDocument.Node n, int nodeNum, int endNum) {
- if (n instanceof HtmlDocument.Text) { // A string node
-
- HtmlDocument.Text textNode = (HtmlDocument.Text) n;
- String str = textNode.getText();
-
- if (preDepth > 0) {
- printer.appendPreText(str);
-
- } else {
- printer.appendNormalText(str);
- }
-
- } else if (n instanceof HtmlDocument.Tag) {
-
- // Check for linebreaking tags.
- HtmlDocument.Tag tag = (HtmlDocument.Tag) n;
- HTML.Element element = tag.getElement();
-
- if (BLANK_LINE_ELEMENTS.contains(element)) {
- printer.setSeparator(PlainTextPrinter.Separator.BlankLine);
-
- } else if (HTML4.BR_ELEMENT.equals(element)) {
- // The <BR> element is special in that it always adds a newline.
- printer.appendForcedLineBreak();
-
- } else if (element.breaksFlow()) {
- // All other elements that break the flow add a LineBreak separator.
- printer.setSeparator(PlainTextPrinter.Separator.LineBreak);
-
- if (HTML4.HR_ELEMENT.equals(element)) {
- printer.appendNormalText("________________________________");
- printer.setSeparator(PlainTextPrinter.Separator.LineBreak);
- }
- }
-
- if (HTML4.BLOCKQUOTE_ELEMENT.equals(element)) {
- printer.incQuoteDepth();
-
- } else if (HTML4.PRE_ELEMENT.equals(element)) {
- preDepth++;
- }
-
- } else if (n instanceof HtmlDocument.EndTag) {
-
- // Check for linebreaking tags.
- HtmlDocument.EndTag endTag = (HtmlDocument.EndTag) n;
- HTML.Element element = endTag.getElement();
-
- if (BLANK_LINE_ELEMENTS.contains(element)) {
- printer.setSeparator(PlainTextPrinter.Separator.BlankLine);
-
- } else if (element.breaksFlow()) {
- // All other elements that break the flow add a LineBreak separator.
- printer.setSeparator(PlainTextPrinter.Separator.LineBreak);
- }
-
- if (HTML4.BLOCKQUOTE_ELEMENT.equals(element)) {
- printer.decQuoteDepth();
-
- } else if (HTML4.PRE_ELEMENT.equals(element)) {
- preDepth--;
- }
- }
- }
-
- public final int getPlainTextLength() {
- return printer.getTextLength();
- }
-
- public final String getPlainText() {
- return printer.getText();
- }
- }
-
- //------------------------------------------------------------------------
- // The following methods are used to build the html tree.
- //------------------------------------------------------------------------
- /** For building the html tree */
- private Stack<Integer> stack;
- private int parent;
-
- /** Starts the build process */
- void start() {
- stack = new Stack<Integer>();
- parent = -1;
- }
-
- /** Finishes the build process */
- void finish() {
- X.assertTrue(stack.size() == 0);
- X.assertTrue(parent == -1);
- }
-
- /**
- * to add the matching end tag
- */
- void addStartTag(HtmlDocument.Tag t) {
- int nodenum = nodes.size();
- addNode(t, nodenum, -1);
-
- stack.add(parent);
- parent = nodenum;
- }
-
- /**
- * Adds a html end tag, this must be preceded by a previous matching open tag
- */
- void addEndTag(HtmlDocument.EndTag t) {
- int nodenum = nodes.size();
- addNode(t, parent, nodenum);
-
- if (parent != -1) {
- ends.set(parent, nodenum);
- }
-
- //is this the right pop?
- parent = stack.pop();
- }
-
- /** Adds a singular tag that does not have a corresponding end tag */
- void addSingularTag(HtmlDocument.Tag t) {
- int nodenum = nodes.size();
- addNode(t, nodenum, nodenum);
- }
-
- /**
- * Adds a text
- * @param t a plain-text string
- */
- void addText(HtmlDocument.Text t) {
- int nodenum = nodes.size();
- addNode(t, nodenum, nodenum);
- }
-
- /** Adds a node */
- private void addNode(HtmlDocument.Node n, int begin, int end) {
-
- nodes.add(n);
- begins.add(begin);
- ends.add(end);
- }
-
- /** For debugging */
- private static final void debug(String str) {
- logger.finest(str);
- }
-
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/html/parser/HtmlTreeBuilder.java b/src/com/android/mail/lib/html/parser/HtmlTreeBuilder.java
deleted file mode 100644
index e5d975d..0000000
--- a/src/com/android/mail/lib/html/parser/HtmlTreeBuilder.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/**
- * Copyright (c) 2004, 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.mail.lib.html.parser;
-
-import com.android.mail.lib.base.X;
-import com.android.mail.lib.html.parser.HtmlDocument.EndTag;
-import com.google.common.io.ByteStreams;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * HtmlTreeBuilder builds a well-formed HtmlTree.
- *
- * @see HtmlTree
- * @author jlim@google.com (Jing Yee Lim)
- */
-public class HtmlTreeBuilder implements HtmlDocument.Visitor {
-
- private static final Logger logger = Logger.getLogger(HtmlTreeBuilder.class.getName());
-
- /** Stack contains HTML4.Element objects to keep track of unclosed tags */
- private final List<HTML.Element> stack = new ArrayList<HTML.Element>();
- private final TableFixer tableFixer = new TableFixer();
- private HtmlTree tree;
- private boolean built = false;
-
- /** Gets the built html tree */
- public HtmlTree getTree() {
- X.assertTrue(built);
- return tree;
- }
-
- /** Implements HtmlDocument.Visitor.start */
- public void start() {
- tree = new HtmlTree();
- tree.start();
- }
-
- /** Implements HtmlDocument.Visitor.finish */
- public void finish() {
- // Close all tags
- while (stack.size() > 0) {
- addMissingEndTag();
- }
- tableFixer.finish();
- tree.finish();
-
- built = true;
- }
-
- /** Implements HtmlDocument.Visitor.visitTag */
- public void visitTag(HtmlDocument.Tag t) {
- tableFixer.seeTag(t);
-
- HTML.Element element = t.getElement();
- if (element.isEmpty()) {
- tree.addSingularTag(t);
- } else if (t.isSelfTerminating()) {
- // Explicitly create a non-selfterminating open tag and add it to the tree
- // and also immediately add the corresponding close tag. This is done
- // so that the toHTML, toXHTML and toOriginalHTML of the tree's node list
- // will be balanced consistently.
- // Otherwise there is a possibility of "<span /></span>" for example, if
- // the created tree is converted to string through toXHTML.
- tree.addStartTag(HtmlDocument.createTag(element,
- t.getAttributes(), t.getOriginalHtmlBeforeAttributes(),
- t.getOriginalHtmlAfterAttributes()));
- EndTag end = HtmlDocument.createEndTag(element);
- tableFixer.seeEndTag(end);
- tree.addEndTag(end);
- } else {
- tree.addStartTag(t);
- push(element); // Track the open tags
- }
- }
-
- /** Implements HtmlVisitor.visit */
- public void visitEndTag(HtmlDocument.EndTag t) {
-
- // Here we pop back to the start tag
- HTML.Element element = t.getElement();
- int pos = findStartTag(element);
- if (pos >= 0) {
-
- // Add missing end-tags if any
- while (pos < stack.size() - 1) {
- addMissingEndTag();
- }
-
- pop();
- tableFixer.seeEndTag(t);
- tree.addEndTag(t);
-
- } else {
- // Not found, ignore this end tag
- logger.finest("Ignoring end tag: " + element.getName());
- }
- }
-
- /** Implements HtmlDocument.Visitor.visitText */
- public void visitText(HtmlDocument.Text t) {
- tableFixer.seeText(t);
- tree.addText(t);
- }
-
- /** Implements HtmlDocument.Visitor.visitComment */
- public void visitComment(HtmlDocument.Comment n) {
- // ignore
- }
-
- /** Finds the start tag from the stack, returns -1 if not found */
- private int findStartTag(HTML.Element element) {
- for (int i = stack.size() - 1; i >= 0; i--) {
- HTML.Element e = stack.get(i);
- if (e == element) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Adds a close tag corresponding to a tag on the stack, if
- * the tag needs a close tag.
- */
- private void addMissingEndTag() {
- HTML.Element element = pop();
-
- HtmlDocument.EndTag endTag = HtmlDocument.createEndTag(element);
- tableFixer.seeEndTag(endTag);
- tree.addEndTag(endTag);
- }
-
- /** Pushes a tag onto the stack */
- private void push(HTML.Element element) {
- stack.add(element);
- }
-
- /** Pops an elemnt from the stack */
- private HTML.Element pop() {
- return stack.remove(stack.size() - 1);
- }
-
- /**
- * The TableFixer makes sure that a <table> structure is more or less well
- * formed. Note that it only ensures that data within the <table> tag doesn't
- * "leak out" of the table.
- *
- * For instance, all the tags here are balanced with end tags. But the
- * 'outside' text ends up leaking out of the table.
- * <table><tr><td bgcolor=yellow>
- * <table><table>inside</table><td>outside</td></table>
- * </td></tr></table>
- *
- * The TableFixer makes sure that
- * 1) Within a table:, text and other elements are enclosed within a TD.
- * A TD tag is inserted where necessary.
- * 2) All table structure tags are enclosed within a <table>. A TABLE tag
- * is inserted where necessary.
- *
- * Note that the TableFixer only adds open tags, it doesn't add end tags.
- * The HtmlTreeVerifier ensures that all open tags are properly matched
- * up and closed.
- *
- * @author Jing Yee Lim (jlim@google.com)
- */
- class TableFixer {
-
- private int tables = 0; // table nesting level
-
- // States within a <table>
- static final int NULL = 0;
- static final int IN_CELL = 1; // in a <td> or <th> tag
- static final int IN_CAPTION = 2; // in a <caption> tag
-
- private int state;
-
- void seeTag(HtmlDocument.Tag tag) {
- HTML.Element element = tag.getElement();
- if (element.getType() == HTML.Element.TABLE_TYPE) {
-
- if (HTML4.TABLE_ELEMENT.equals(element)) {
- if (tables > 0) {
- ensureCellState();
- }
- tables++;
- state = NULL;
-
- } else {
- // Make sure that we're in a table
- ensureTableState();
-
- // In cell/caption?
- if (HTML4.TD_ELEMENT.equals(element) ||
- HTML4.TH_ELEMENT.equals(element)) {
- state = IN_CELL;
-
- } else if (HTML4.CAPTION_ELEMENT.equals(element)) {
- state = IN_CAPTION;
- }
- }
- } else {
- if (tables > 0) {
-
- // Ok to have a form element outside a table cell.
- // e.g. <TR><FORM><TD>...
- if (!HTML4.FORM_ELEMENT.equals(element)) {
- ensureCellState();
- }
- }
- }
- }
-
- void seeEndTag(HtmlDocument.EndTag endTag) {
- HTML.Element element= endTag.getElement();
-
- if (tables > 0 && element.getType() == HTML.Element.TABLE_TYPE) {
-
- if (HTML4.TD_ELEMENT.equals(element) ||
- HTML4.TR_ELEMENT.equals(element) ||
- HTML4.TH_ELEMENT.equals(element)) {
- // End of a cell
- state = NULL;
-
- } else if (HTML4.CAPTION_ELEMENT.equals(element)) { // End caption
- state = NULL;
-
- } else if (HTML4.TABLE_ELEMENT.equals(element)) { // End table
- X.assertTrue(tables > 0);
- tables--;
- state = (tables > 0) ? IN_CELL : NULL;
- }
- }
- }
-
- void seeText(HtmlDocument.Text textNode) {
- // If we're in a table, but not in a cell or caption, and the
- // text is not whitespace, add a <TD>
- if (tables > 0 &&
- state == NULL &&
- !textNode.isWhitespace()) {
- ensureCellState();
- }
- }
-
- void finish() {
- X.assertTrue(tables == 0);
- X.assertTrue(state == NULL);
- }
-
- // Ensure that we're within a TABLE
- private void ensureTableState() {
- if (tables == 0) {
- push(HTML4.TABLE_ELEMENT);
-
- HtmlDocument.Tag tableTag =
- HtmlDocument.createTag(HTML4.TABLE_ELEMENT, null);
- tree.addStartTag(tableTag);
-
- tables++;
- }
- }
-
- // Ensure that we're within a TD or TH cell
- private void ensureCellState() {
- if (state != IN_CELL) {
- push(HTML4.TD_ELEMENT);
-
- HtmlDocument.Tag tdTag = HtmlDocument.createTag(HTML4.TD_ELEMENT, null);
- tree.addStartTag(tdTag);
-
- state = IN_CELL;
- }
- }
- }
-
- /** For testing */
- public static void main(String[] args) throws IOException {
- logger.setLevel(Level.FINEST);
-
- String html = new String(ByteStreams.toByteArray(System.in));
- HtmlParser parser = new HtmlParser();
- HtmlDocument doc = parser.parse(html);
-
- HtmlTreeBuilder builder = new HtmlTreeBuilder();
- doc.accept(builder);
- String outputHtml = builder.getTree().getHtml();
-
- System.out.println(outputHtml);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/mail/lib/html/parser/HtmlWhitelist.java b/src/com/android/mail/lib/html/parser/HtmlWhitelist.java
deleted file mode 100644
index 836633b..0000000
--- a/src/com/android/mail/lib/html/parser/HtmlWhitelist.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Copyright (c) 2004, 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.mail.lib.html.parser;
-
-/**
- * HtmlWhitelist is an interface that defines methods required by HtmlParser for
- * looking up accepted HTML elements and attributes.
- *
- * @author sammy@google.com (Sammy Leong)
- */
-public interface HtmlWhitelist {
- /**
- * Looks up the HTML.Element object associated with the given element tag
- * name.
- *
- * @param name The tag name of the element to lookup
- * @return The HTML.Element object associated with the given element tag name,
- * or null if the given name is not in the whitelist.
- */
- HTML.Element lookupElement(String name);
-
- /**
- * Looks up the HTML.Attribute object associated with the given attribute
- * name.
- *
- * @param name The name of the attribute to lookup
- * @return The HTML.Attribute object associated with the given attribute name,
- * or null if the given name is not in the whitelist.
- */
- HTML.Attribute lookupAttribute(String name);
-}
\ No newline at end of file
diff --git a/src/com/android/mail/preferences/MailPrefs.java b/src/com/android/mail/preferences/MailPrefs.java
index 4d6957d..01a58ce 100644
--- a/src/com/android/mail/preferences/MailPrefs.java
+++ b/src/com/android/mail/preferences/MailPrefs.java
@@ -113,10 +113,25 @@
public static final String CONVERSATION_OVERVIEW_MODE = "conversation-overview-mode";
+ public static final String ALWAYS_LAUNCH_GMAIL_FROM_EMAIL_TOMBSTONE =
+ "always-launch-gmail-from-email-tombstone";
+
public static final String SNAP_HEADER_MODE = "snap-header-mode";
public static final String RECENT_ACCOUNTS = "recent-accounts";
+ public static final String REQUIRED_SANITIZER_VERSION_NUMBER = "required-sanitizer-version-number";
+
+ public static final String MIGRATION_STATE = "migration-state";
+
+ // State indicating that no migration has yet occurred.
+ public static final int MIGRATION_STATE_NONE = 0;
+ // State indicating that we have migrated imap and pop accounts, but not
+ // Exchange accounts.
+ public static final int MIGRATION_STATE_IMAP_POP = 1;
+ // State indicating that we have migrated all accounts.
+ public static final int MIGRATION_STATE_ALL = 2;
+
public static final ImmutableSet<String> BACKUP_KEYS =
new ImmutableSet.Builder<String>()
.add(DEFAULT_REPLY_ALL)
@@ -507,6 +522,18 @@
return getSharedPreferences().contains(PreferenceKeys.CONVERSATION_OVERVIEW_MODE);
}
+ public void setAlwaysLaunchGmailFromEmailTombstone(final boolean alwaysLaunchGmail) {
+ getEditor()
+ .putBoolean(PreferenceKeys.ALWAYS_LAUNCH_GMAIL_FROM_EMAIL_TOMBSTONE,
+ alwaysLaunchGmail)
+ .apply();
+ }
+
+ public boolean getAlwaysLaunchGmailFromEmailTombstone() {
+ return getSharedPreferences()
+ .getBoolean(PreferenceKeys.ALWAYS_LAUNCH_GMAIL_FROM_EMAIL_TOMBSTONE, false);
+ }
+
public void setSnapHeaderMode(final int snapHeaderMode) {
getEditor().putInt(PreferenceKeys.SNAP_HEADER_MODE, snapHeaderMode).apply();
}
@@ -520,6 +547,15 @@
return mSnapHeaderDefault;
}
+ public int getMigrationState() {
+ return getSharedPreferences()
+ .getInt(PreferenceKeys.MIGRATION_STATE, PreferenceKeys.MIGRATION_STATE_NONE);
+ }
+
+ public void setMigrationState(final int state) {
+ getEditor().putInt(PreferenceKeys.MIGRATION_STATE, state).apply();
+ }
+
public Set<String> getRecentAccounts() {
return getSharedPreferences().getStringSet(PreferenceKeys.RECENT_ACCOUNTS, null);
}
@@ -527,4 +563,22 @@
public void setRecentAccounts(Set<String> recentAccounts) {
getEditor().putStringSet(PreferenceKeys.RECENT_ACCOUNTS, recentAccounts).apply();
}
+
+ /**
+ * Returns the minimum version number of the {@link com.android.mail.utils.HtmlSanitizer} which
+ * is trusted. If the version of the HtmlSanitizer does not meet or exceed this value,
+ * sanitization will be deemed untrustworthy and emails will be displayed in a sandbox that does
+ * not allow script execution.
+ */
+ public int getRequiredSanitizerVersionNumber() {
+ return getSharedPreferences().getInt(PreferenceKeys.REQUIRED_SANITIZER_VERSION_NUMBER, 1);
+ }
+
+ /**
+ * @param versionNumber the minimum version number of the
+ * {@link com.android.mail.utils.HtmlSanitizer} which produces trusted output
+ */
+ public void setRequiredSanitizerVersionNumber(int versionNumber) {
+ getEditor().putInt(PreferenceKeys.REQUIRED_SANITIZER_VERSION_NUMBER, versionNumber).apply();
+ }
}
diff --git a/src/com/android/mail/print/PrintUtils.java b/src/com/android/mail/print/PrintUtils.java
index ee04d9c..f9b4636 100644
--- a/src/com/android/mail/print/PrintUtils.java
+++ b/src/com/android/mail/print/PrintUtils.java
@@ -85,21 +85,22 @@
*
* Sets up a webview to perform the printing work.
*/
- @SuppressLint("NewApi")
+ @SuppressLint({"NewApi", "SetJavaScriptEnabled"})
private static void printHtml(Context context, String html,
String baseUri, String subject, boolean useJavascript) {
final WebView webView = new WebView(context);
final WebSettings settings = webView.getSettings();
settings.setBlockNetworkImage(false);
settings.setJavaScriptEnabled(useJavascript);
- webView.loadDataWithBaseURL(baseUri, html,
- "text/html", "utf-8", null);
+ webView.loadDataWithBaseURL(baseUri, html, "text/html", "utf-8", null);
final PrintManager printManager =
(PrintManager) context.getSystemService(Context.PRINT_SERVICE);
final String printJobName = buildPrintJobName(context, subject);
printManager.print(printJobName,
- webView.createPrintDocumentAdapter(),
+ Utils.isRunningLOrLater() ?
+ webView.createPrintDocumentAdapter(printJobName) :
+ webView.createPrintDocumentAdapter(),
new PrintAttributes.Builder().build());
}
diff --git a/src/com/android/mail/providers/Account.java b/src/com/android/mail/providers/Account.java
index 0fe63f1..9a389f9 100644
--- a/src/com/android/mail/providers/Account.java
+++ b/src/com/android/mail/providers/Account.java
@@ -18,7 +18,6 @@
import android.content.ContentResolver;
import android.content.ContentValues;
-import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
@@ -26,17 +25,16 @@
import android.os.Parcelable;
import android.text.TextUtils;
-import com.android.mail.R;
import com.android.mail.content.CursorCreator;
import com.android.mail.content.ObjectCursor;
-import com.android.mail.lib.base.Preconditions;
-import com.android.mail.lib.base.Strings;
import com.android.mail.providers.UIProvider.AccountCapabilities;
import com.android.mail.providers.UIProvider.AccountColumns;
import com.android.mail.providers.UIProvider.SyncStatus;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.Utils;
+import com.google.android.mail.common.base.Preconditions;
+import com.google.android.mail.common.base.Strings;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
diff --git a/src/com/android/mail/providers/Folder.java b/src/com/android/mail/providers/Folder.java
index e278915..4b1e904 100644
--- a/src/com/android/mail/providers/Folder.java
+++ b/src/com/android/mail/providers/Folder.java
@@ -29,6 +29,7 @@
import android.view.View;
import android.widget.ImageView;
+import com.android.mail.R;
import com.android.mail.content.CursorCreator;
import com.android.mail.content.ObjectCursorLoader;
import com.android.mail.providers.UIProvider.FolderType;
@@ -640,7 +641,13 @@
if (iconView == null) {
return;
}
- final int icon = folder.iconResId;
+ int icon = folder.iconResId;
+
+ // If we're using the default folders, make sure we show the parent icon
+ if (icon == R.drawable.ic_drawer_folder_24dp && folder.hasChildren) {
+ icon = R.drawable.ic_folder_parent_24dp;
+ }
+
if (icon > 0) {
final Drawable iconDrawable = iconView.getResources().getDrawable(icon);
if (iconDrawable != null &&
diff --git a/src/com/android/mail/providers/SearchRecentSuggestionsProvider.java b/src/com/android/mail/providers/SearchRecentSuggestionsProvider.java
index 30fa9dd..05667c7 100644
--- a/src/com/android/mail/providers/SearchRecentSuggestionsProvider.java
+++ b/src/com/android/mail/providers/SearchRecentSuggestionsProvider.java
@@ -198,7 +198,7 @@
ContentValues values = new ContentValues(3);
values.put("display1", query);
values.put("query", query);
- values.put("date", SystemClock.elapsedRealtime());
+ values.put("date", System.currentTimeMillis());
// Note: This table has on-conflict-replace semantics, so insert() may actually replace()
db.insert(sSuggestions, null, values);
}
diff --git a/src/com/android/mail/providers/Settings.java b/src/com/android/mail/providers/Settings.java
index 683ee29..b179239 100644
--- a/src/com/android/mail/providers/Settings.java
+++ b/src/com/android/mail/providers/Settings.java
@@ -22,7 +22,6 @@
import android.os.Parcelable;
import android.text.TextUtils;
-import com.android.mail.lib.base.Strings;
import com.android.mail.providers.UIProvider.AccountColumns.SettingsColumns;
import com.android.mail.providers.UIProvider.AutoAdvance;
import com.android.mail.providers.UIProvider.ConversationListIcon;
@@ -32,6 +31,7 @@
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.Utils;
+import com.google.android.mail.common.base.Strings;
import com.google.common.base.Objects;
import org.json.JSONException;
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index 7f22bca..a280c55 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -238,7 +238,7 @@
/**
* Whether the server sends us sanitized HTML (guaranteed to not contain malicious HTML).
*/
- public static final int SANITIZED_HTML = 0x0080;
+ public static final int SERVER_SANITIZED_HTML = 0x0080;
/**
* Whether the server allows synchronization of draft messages. This does NOT require
* SYNCABLE_FOLDERS to be set.
@@ -316,6 +316,10 @@
* Whether the account supports nested folders
*/
public static final int NESTED_FOLDERS = 0x800000;
+ /**
+ * Whether the client is permitted to sanitize HTML for this account.
+ */
+ public static final int CLIENT_SANITIZED_HTML = 0x1000000;
}
public static final class AccountColumns implements BaseColumns {
diff --git a/src/com/android/mail/providers/protos/mock/MockUiProvider.java b/src/com/android/mail/providers/protos/mock/MockUiProvider.java
index dcd4019..37a219b 100644
--- a/src/com/android/mail/providers/protos/mock/MockUiProvider.java
+++ b/src/com/android/mail/providers/protos/mock/MockUiProvider.java
@@ -318,7 +318,7 @@
AccountCapabilities.MUTE |
AccountCapabilities.SERVER_SEARCH |
AccountCapabilities.FOLDER_SERVER_SEARCH |
- AccountCapabilities.SANITIZED_HTML |
+ AccountCapabilities.SERVER_SANITIZED_HTML |
AccountCapabilities.DRAFT_SYNCHRONIZATION |
AccountCapabilities.MULTIPLE_FROM_ADDRESSES |
AccountCapabilities.SMART_REPLY |
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index a865eb9..2114aae 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -1659,7 +1659,11 @@
// Temporarily remove the ConversationFooterItem and its view.
// It will get re-added right after the new message is added.
final ConversationFooterItem footerItem = mAdapter.removeFooterItem();
- mConversationContainer.removeViewAtAdapterIndex(footerItem.getPosition());
+ // if no footer, just skip the work for it. The rest should be fine to do.
+ if (footerItem != null) {
+ mConversationContainer.removeViewAtAdapterIndex(footerItem.getPosition());
+ }
+
mTemplates.reset();
// this method will add some items to mAdapter, but we deliberately want to avoid notifying
// adapter listeners (i.e. ConversationContainer) until onWebContentGeometryChange is next
diff --git a/src/com/android/mail/ui/LeaveBehindItem.java b/src/com/android/mail/ui/LeaveBehindItem.java
index afd44fe..45e5104 100644
--- a/src/com/android/mail/ui/LeaveBehindItem.java
+++ b/src/com/android/mail/ui/LeaveBehindItem.java
@@ -103,9 +103,6 @@
cursor.undo(getContext(), mAccount.undoUri);
}
}
- } else if (id == R.id.undo_descriptionview) {
- // Essentially, makes sure that tapping description view doesn't highlight
- // either the undo button icon or text.
}
}
@@ -122,7 +119,7 @@
// and button text as selected since they set duplicateParentState to true
mSwipeableContent.setOnClickListener(this);
mSwipeableContent.setAlpha(TRANSPARENT);
- mText = ((TextView) findViewById(R.id.undo_descriptionview));
+ mText = ((TextView) findViewById(R.id.undo_description_text));
mText.setText(Utils.convertHtmlToPlainText(mUndoOp
.getSingularDescription(getContext(), folder)));
mText.setOnClickListener(this);
@@ -164,10 +161,7 @@
/**
* Animate shrinking the height of this view.
- * @param item the conversation to animate
* @param listener the method to call when the animation is done
- * @param undo true if an operation is being undone. We animate the item
- * away during delete. Undoing populates the item.
*/
public void startShrinkAnimation(AnimatorListener listener) {
if (!mAnimating) {
diff --git a/src/com/android/mail/ui/MaterialSearchActionView.java b/src/com/android/mail/ui/MaterialSearchActionView.java
index c726b69..03f6a54 100644
--- a/src/com/android/mail/ui/MaterialSearchActionView.java
+++ b/src/com/android/mail/ui/MaterialSearchActionView.java
@@ -36,7 +36,7 @@
* Custom view for the action bar when search is displayed.
*/
public class MaterialSearchActionView extends LinearLayout implements TextWatcher,
- View.OnClickListener, TextView.OnEditorActionListener {
+ View.OnClickListener, TextView.OnEditorActionListener, View.OnKeyListener {
private MaterialSearchViewController mController;
private InputMethodManager mImm;
private boolean mShowingClose;
@@ -86,6 +86,7 @@
mQueryText.addTextChangedListener(this);
mQueryText.setOnClickListener(this);
mQueryText.setOnEditorActionListener(this);
+ mQueryText.setOnKeyListener(this);
mEndingButton = (ImageView) findViewById(R.id.search_actionbar_ending_button);
mEndingButton.setOnClickListener(this);
}
@@ -137,4 +138,14 @@
}
return false;
}
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // Hardware keyboard doesn't represent Enter as Search through imeOptions, so we need to
+ // capture them manually here.
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) {
+ mController.onSearchPerformed(mQueryText.getText().toString());
+ }
+ return false;
+ }
}
diff --git a/src/com/android/mail/ui/MaterialSearchViewController.java b/src/com/android/mail/ui/MaterialSearchViewController.java
index 1ea28dc..4e5b5a4 100644
--- a/src/com/android/mail/ui/MaterialSearchViewController.java
+++ b/src/com/android/mail/ui/MaterialSearchViewController.java
@@ -63,8 +63,7 @@
private int mViewMode;
private int mViewState;
- private boolean mSavePending;
- private boolean mNeedToDestroyProvider;
+ private boolean mWaitToDestroyProvider;
public MaterialSearchViewController(MailActivity activity, ActivityController controller,
Intent intent, Bundle savedInstanceState) {
@@ -92,8 +91,7 @@
}
public void onDestroy() {
- mNeedToDestroyProvider = mSavePending;
- if (!mSavePending) {
+ if (!mWaitToDestroyProvider) {
mSuggestionsProvider.cleanup();
}
mActivity.getViewMode().removeListener(this);
@@ -204,7 +202,7 @@
@Override
protected void onPreExecute() {
- mSavePending = true;
+ mWaitToDestroyProvider = true;
}
@Override
@@ -215,9 +213,9 @@
@Override
protected void onPostExecute(Void aVoid) {
- mSavePending = false;
- if (mNeedToDestroyProvider) {
+ if (mWaitToDestroyProvider) {
mSuggestionsProvider.cleanup();
+ mWaitToDestroyProvider = false;
}
}
}
diff --git a/src/com/android/mail/ui/MiniDrawerView.java b/src/com/android/mail/ui/MiniDrawerView.java
index a23c7d8..656fc27 100644
--- a/src/com/android/mail/ui/MiniDrawerView.java
+++ b/src/com/android/mail/ui/MiniDrawerView.java
@@ -85,7 +85,7 @@
}
public void refresh() {
- if (mController == null) {
+ if (mController == null || !mController.isAdded()) {
return;
}
diff --git a/src/com/android/mail/ui/NestedFolderTeaserView.java b/src/com/android/mail/ui/NestedFolderTeaserView.java
index c75b5ce..f3fe63e 100644
--- a/src/com/android/mail/ui/NestedFolderTeaserView.java
+++ b/src/com/android/mail/ui/NestedFolderTeaserView.java
@@ -115,14 +115,16 @@
private final View mItemView;
private final TextView mSendersTextView;
private final TextView mCountTextView;
+ private final ImageView mFolderIconImageView;
private Folder mFolder;
private List<String> mUnreadSenders = ImmutableList.of();
public FolderHolder(final View itemView, final TextView sendersTextView,
- final TextView countTextView) {
+ final TextView countTextView, final ImageView folderIconImageView) {
mItemView = itemView;
mSendersTextView = sendersTextView;
mCountTextView = countTextView;
+ mFolderIconImageView = folderIconImageView;
}
public void setFolder(final Folder folder) {
@@ -141,6 +143,8 @@
return mCountTextView;
}
+ public ImageView getFolderIconImageView() { return mFolderIconImageView; }
+
public Folder getFolder() {
return mFolder;
}
@@ -218,7 +222,10 @@
((TextView) itemView.findViewById(R.id.folder_textView)).setText(folderName);
final TextView sendersTextView = (TextView) itemView.findViewById(R.id.senders_textView);
final TextView countTextView = (TextView) itemView.findViewById(R.id.unread_count_textView);
- final FolderHolder holder = new FolderHolder(itemView, sendersTextView, countTextView);
+ final ImageView folderIconImageView =
+ (ImageView) itemView.findViewById(R.id.nested_folder_icon);
+ final FolderHolder holder = new FolderHolder(itemView, sendersTextView, countTextView,
+ folderIconImageView);
countTextView.setVisibility(View.VISIBLE);
attachOnClickListener(itemView, holder);
@@ -432,7 +439,7 @@
if (data.moveToFirst()) {
do {
final Folder folder = data.getModel();
- final FolderHolder holder = mFolderHolders.get(folder.id);
+ FolderHolder holder = mFolderHolders.get(folder.id);
if (holder != null) {
final Folder oldFolder = holder.getFolder();
@@ -448,21 +455,26 @@
}
} else {
// Create the holder, and init a loader
- final FolderHolder newHolder = createFolderHolder(folder.name);
- newHolder.setFolder(folder);
- mFolderHolders.put(folder.id, newHolder);
+ holder = createFolderHolder(folder.name);
+ holder.setFolder(folder);
+ mFolderHolders.put(folder.id, holder);
// We can not support displaying sender info with nested folders
// because it doesn't scale. Disabling it for now, until we can
// optimize it.
// initFolderLoader(getLoaderId(folder.id));
- populateUnreadSenders(newHolder, folder.unreadSenders);
+ populateUnreadSenders(holder, folder.unreadSenders);
- updateViews(newHolder);
+ updateViews(holder);
mListUpdated = true;
}
+ if (folder.hasChildren) {
+ holder.getFolderIconImageView().setImageDrawable(
+ getResources().getDrawable(R.drawable.ic_folder_parent_24dp));
+ }
+
// Note: #remove(int) removes from that POSITION
// #remove(Integer) removes that OBJECT
oldFolderIds.remove(Integer.valueOf(folder.id));
diff --git a/src/com/android/mail/utils/AttachmentUtils.java b/src/com/android/mail/utils/AttachmentUtils.java
index 85f5206..4837f7a 100644
--- a/src/com/android/mail/utils/AttachmentUtils.java
+++ b/src/com/android/mail/utils/AttachmentUtils.java
@@ -231,7 +231,7 @@
}
return cachedFileUri;
- } catch (IOException e) {
+ } catch (IOException | SecurityException e) {
// Catch any exception here to allow for unexpected failures during caching se we don't
// leave app in inconsistent state as we call this method outside of a transaction for
// performance reasons.
diff --git a/src/com/android/mail/utils/HtmlSanitizer.java b/src/com/android/mail/utils/HtmlSanitizer.java
index 976bdaa..ca831df 100644
--- a/src/com/android/mail/utils/HtmlSanitizer.java
+++ b/src/com/android/mail/utils/HtmlSanitizer.java
@@ -44,6 +44,17 @@
* or comparable.
*/
public final class HtmlSanitizer {
+
+ /**
+ * This version number should be bumped each time a meaningful change is made to this sanitizer
+ * configuration which influences its output. It is compared against a minimum target version
+ * number. If it meets or exceeds the minimum target version, the result of the sanitizer is
+ * free to be shown in a standard webview. If it does not meet the minimum target version then
+ * the sanitized output is deemed untrustworthy and is shown in a sandboxed webview with
+ * javascript execution disabled.
+ */
+ public static final int VERSION = 1;
+
private static final String LOG_TAG = LogTag.getLogTag();
/**
@@ -90,7 +101,7 @@
final String value = attrs.remove(idIndex + 1);
attrs.remove(idIndex);
- // AOL uses a specifc id value to indicate quoted text
+ // AOL uses a specific id value to indicate quoted text
showHideQuotedText = value.startsWith("AOLMsgPart");
}
@@ -105,10 +116,22 @@
};
/**
- * Disallow the "mailto:" url on images so that "Show pictures" can't be used to start composing
- * a bajillion emails.
+ * Disallow "cid:" and "mailto:" urls on all tags not <a> or <img>.
*/
- private static final AttributePolicy NO_MAILTO_URL =
+ private static final AttributePolicy URL_PROTOCOLS =
+ new FilterUrlByProtocolAttributePolicy(ImmutableList.of("http", "https"));
+
+ /**
+ * Disallow the "cid:" url on links. Do allow "mailto:" urls to support sending mail.
+ */
+ private static final AttributePolicy A_HREF_PROTOCOLS =
+ new FilterUrlByProtocolAttributePolicy(ImmutableList.of("mailto", "http", "https"));
+
+ /**
+ * Disallow the "mailto:" url on images so that "Show pictures" can't be used to start composing
+ * a bajillion emails. Do allow "cid:" urls to support inline image attachments.
+ */
+ private static final AttributePolicy IMG_SRC_PROTOCOLS =
new FilterUrlByProtocolAttributePolicy(ImmutableList.of("cid", "http", "https"));
/**
@@ -169,17 +192,20 @@
.allowUrlProtocols("cid", "http", "https", "mailto")
.allowStyling(CssSchema.union(CssSchema.DEFAULT, ADDITIONAL_CSS))
.disallowTextIn("applet", "frameset", "object", "script", "style", "title")
- .allowElements("a").allowAttributes("coords", "href", "name", "shape").onElements("a")
+ .allowElements("a")
+ .allowAttributes("coords", "name", "shape").onElements("a")
+ .allowAttributes("href").matching(A_HREF_PROTOCOLS).onElements("a")
.allowElements("abbr").allowAttributes("title").onElements("abbr")
.allowElements("acronym").allowAttributes("title").onElements("acronym")
.allowElements("address")
.allowElements("area")
- .allowAttributes("alt", "coords", "href", "nohref", "name", "shape")
- .onElements("area")
+ .allowAttributes("alt", "coords", "nohref", "name", "shape").onElements("area")
+ .allowAttributes("href").matching(URL_PROTOCOLS).onElements("area")
.allowElements("article")
.allowElements("aside")
.allowElements("b")
- .allowElements("base").allowAttributes("href").onElements("base")
+ .allowElements("base")
+ .allowAttributes("href").matching(URL_PROTOCOLS).onElements("base")
.allowElements("bdi").allowAttributes("dir").onElements("bdi")
.allowElements("bdo").allowAttributes("dir").onElements("bdo")
.allowElements("big")
@@ -196,7 +222,7 @@
.allowElements("cite")
.allowElements("code")
.allowElements("col")
- . allowAttributes("align", "bgcolor", "char", "charoff", "span", "valign", "width")
+ .allowAttributes("align", "bgcolor", "char", "charoff", "span", "valign", "width")
.onElements("col")
.allowElements("colgroup")
.allowAttributes("align", "char", "charoff", "span", "valign", "width")
@@ -236,18 +262,23 @@
.onElements("hr")
.allowElements("i")
.allowElements("img")
+ .allowAttributes("src").matching(IMG_SRC_PROTOCOLS).onElements("img")
+ .allowAttributes("longdesc").matching(URL_PROTOCOLS).onElements("img")
.allowAttributes("align", "alt", "border", "crossorigin", "height", "hspace",
- "ismap", "longdesc", "usemap", "vspace", "width")
+ "ismap", "usemap", "vspace", "width")
.onElements("img")
- .allowAttributes("src").matching(NO_MAILTO_URL).onElements("img")
.allowElements("input")
+ .allowAttributes("src").matching(URL_PROTOCOLS).onElements("input")
+ .allowAttributes("formaction").matching(URL_PROTOCOLS).onElements("input")
.allowAttributes("accept", "align", "alt", "autocomplete", "autofocus", "checked",
- "disabled", "form", "formaction", "formenctype", "formmethod",
- "formnovalidate", "formtarget", "height", "list", "max", "maxlength", "min",
- "multiple", "name", "pattern", "placeholder", "readonly", "required",
- "size", "src", "step", "type", "value", "width")
+ "disabled", "form", "formenctype", "formmethod", "formnovalidate",
+ "formtarget", "height", "list", "max", "maxlength", "min", "multiple",
+ "name", "pattern", "placeholder", "readonly", "required", "size", "step",
+ "type", "value", "width")
.onElements("input")
- .allowElements("ins").allowAttributes("cite", "datetime").onElements("ins")
+ .allowElements("ins")
+ .allowAttributes("cite").matching(URL_PROTOCOLS).onElements("ins")
+ .allowAttributes("datetime").onElements("ins")
.allowElements("kbd")
.allowElements("keygen")
.allowAttributes("autofocus", "challenge", "disabled", "form", "keytype", "name")
@@ -260,9 +291,9 @@
.allowElements("mark")
.allowElements("menu").allowAttributes("label", "type").onElements("menu")
.allowElements("menuitem")
- .allowAttributes("checked", "command", "default", "disabled", "icon", "label",
- "type", "radiogroup")
- .onElements("menuitem")
+ .allowAttributes("icon").matching(URL_PROTOCOLS).onElements("menuitem")
+ .allowAttributes("checked", "command", "default", "disabled", "label", "type",
+ "radiogroup").onElements("menuitem")
.allowElements("meter")
.allowAttributes("form", "high", "low", "max", "min", "optimum", "value")
.onElements("meter")
@@ -278,7 +309,7 @@
.allowElements("p").allowAttributes("align").onElements("p")
.allowElements("pre").allowAttributes("width").onElements("pre")
.allowElements("progress").allowAttributes("max", "value").onElements("progress")
- .allowElements("q").allowAttributes("cite").onElements("q")
+ .allowElements("q").allowAttributes("cite").matching(URL_PROTOCOLS).onElements("q")
.allowElements("rp")
.allowElements("rt")
.allowElements("ruby")
diff --git a/src/com/android/mail/utils/NotificationUtils.java b/src/com/android/mail/utils/NotificationUtils.java
index 56704b4..d470a29 100644
--- a/src/com/android/mail/utils/NotificationUtils.java
+++ b/src/com/android/mail/utils/NotificationUtils.java
@@ -96,6 +96,7 @@
public static final String EXTRA_UNSEEN_COUNT = "unseen-count";
public static final String EXTRA_GET_ATTENTION = "get-attention";
private static final int PUBLIC_NOTIFICATIONS_VISIBLE_CHARS = 4;
+ private static final int MAX_CHARS = 5120;
/** Contains a list of <(account, label), unread conversations> */
private static NotificationMap sActiveNotificationMap = null;
@@ -631,7 +632,7 @@
if (com.android.mail.utils.Utils.isRunningLOrLater()) {
notification.setColor(
- context.getResources().getColor(R.color.notification_icon_gmail_red));
+ context.getResources().getColor(R.color.notification_icon_color));
}
if(unseenCount > 1) {
@@ -865,7 +866,7 @@
.setWhen(when);
if (com.android.mail.utils.Utils.isRunningLOrLater()) {
- builder.setColor(context.getResources().getColor(R.color.notification_icon_gmail_red));
+ builder.setColor(context.getResources().getColor(R.color.notification_icon_color));
}
// if this public notification summarizes multiple single notifications, mark it as the
@@ -1082,7 +1083,7 @@
if (com.android.mail.utils.Utils.isRunningLOrLater()) {
conversationNotif.setColor(
context.getResources()
- .getColor(R.color.notification_icon_gmail_red));
+ .getColor(R.color.notification_icon_color));
}
conversationNotif.setContentText(digestLine);
Intent conversationNotificationIntent = createViewConversationIntent(
@@ -1504,7 +1505,10 @@
final TextAppearanceSpan notificationSubjectSpan = new TextAppearanceSpan(
context, R.style.NotificationPrimaryText);
- final String snippet = getMessageBodyWithoutElidedText(message);
+ String snippet = getMessageBodyWithoutElidedText(message);
+ if (snippet.length() > MAX_CHARS) {
+ snippet = snippet.substring(0, MAX_CHARS);
+ }
// Change multiple newlines (with potential white space between), into a single new line
final String collapsedSnippet =
@@ -1665,7 +1669,7 @@
}
final ContactIconInfo contactIconInfo;
- if (senderAddress == null) {
+ if (TextUtils.isEmpty(senderAddress)) {
contactIconInfo = new ContactIconInfo();
} else {
// Get the ideal size for this icon.
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 05f3665..af95b9e 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -20,14 +20,14 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Fragment;
-import android.app.SearchManager;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.Typeface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
@@ -37,12 +37,9 @@
import android.provider.Browser;
import android.support.v4.text.TextUtilsCompat;
import android.support.v4.view.ViewCompat;
-import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
-import android.text.style.CharacterStyle;
-import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.view.Menu;
import android.view.MenuItem;
@@ -132,8 +129,6 @@
public static final SimpleTimer sConvLoadTimer =
new SimpleTimer(ENABLE_CONV_LOAD_TIMER).withSessionName("ConvLoadTimer");
- private static final int[] STYLE_ATTR = new int[] {android.R.attr.background};
-
public static boolean isRunningJellybeanOrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
}
@@ -250,13 +245,87 @@
return text.substring(0, realMax) + extension;
}
+ /**
+ * This lock must be held before accessing any of the following fields
+ */
+ private static final Object sStaticResourcesLock = new Object();
+ private static ComponentCallbacksListener sComponentCallbacksListener;
private static int sMaxUnreadCount = -1;
- private static final CharacterStyle ACTION_BAR_UNREAD_STYLE = new StyleSpan(Typeface.BOLD);
private static String sUnreadText;
private static String sUnseenText;
private static String sLargeUnseenText;
private static int sDefaultFolderBackgroundColor = -1;
- private static int sUseFolderListFragmentTransition = -1;
+
+ private static class ComponentCallbacksListener implements ComponentCallbacks {
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ synchronized (sStaticResourcesLock) {
+ sMaxUnreadCount = -1;
+ sUnreadText = null;
+ sUnseenText = null;
+ sLargeUnseenText = null;
+ sDefaultFolderBackgroundColor = -1;
+ }
+ }
+
+ @Override
+ public void onLowMemory() {}
+ }
+
+ public static void getStaticResources(Context context) {
+ synchronized (sStaticResourcesLock) {
+ if (sUnreadText == null) {
+ final Resources r = context.getResources();
+ sMaxUnreadCount = r.getInteger(R.integer.maxUnreadCount);
+ sUnreadText = r.getString(R.string.widget_large_unread_count);
+ sUnseenText = r.getString(R.string.unseen_count);
+ sLargeUnseenText = r.getString(R.string.large_unseen_count);
+ sDefaultFolderBackgroundColor = r.getColor(R.color.default_folder_background_color);
+
+ if (sComponentCallbacksListener == null) {
+ sComponentCallbacksListener = new ComponentCallbacksListener();
+ context.getApplicationContext()
+ .registerComponentCallbacks(sComponentCallbacksListener);
+ }
+ }
+ }
+ }
+
+ private static int getMaxUnreadCount(Context context) {
+ synchronized (sStaticResourcesLock) {
+ getStaticResources(context);
+ return sMaxUnreadCount;
+ }
+ }
+
+ private static String getUnreadText(Context context) {
+ synchronized (sStaticResourcesLock) {
+ getStaticResources(context);
+ return sUnreadText;
+ }
+ }
+
+ private static String getUnseenText(Context context) {
+ synchronized (sStaticResourcesLock) {
+ getStaticResources(context);
+ return sUnseenText;
+ }
+ }
+
+ private static String getLargeUnseenText(Context context) {
+ synchronized (sStaticResourcesLock) {
+ getStaticResources(context);
+ return sLargeUnseenText;
+ }
+ }
+
+ public static int getDefaultFolderBackgroundColor(Context context) {
+ synchronized (sStaticResourcesLock) {
+ getStaticResources(context);
+ return sDefaultFolderBackgroundColor;
+ }
+ }
/**
* Returns a boolean indicating whether the table UI should be shown.
@@ -266,18 +335,6 @@
}
/**
- * Returns a boolean indicating whether or not we should animate in the
- * folder list fragment.
- */
- public static boolean useFolderListFragmentTransition(Context context) {
- if (sUseFolderListFragmentTransition == -1) {
- sUseFolderListFragmentTransition = context.getResources().getInteger(
- R.integer.use_folder_list_fragment_transition);
- }
- return sUseFolderListFragmentTransition != 0;
- }
-
- /**
* Returns displayable text from the provided HTML string.
* @param htmlText HTML string
* @return Plain text string representation of the specified Html string
@@ -361,16 +418,11 @@
*/
public static String getUnreadCountString(Context context, int unreadCount) {
final String unreadCountString;
- final Resources resources = context.getResources();
- if (sMaxUnreadCount == -1) {
- sMaxUnreadCount = resources.getInteger(R.integer.maxUnreadCount);
- }
- if (unreadCount > sMaxUnreadCount) {
- if (sUnreadText == null) {
- sUnreadText = resources.getString(R.string.widget_large_unread_count);
- }
+ final int maxUnreadCount = getMaxUnreadCount(context);
+ if (unreadCount > maxUnreadCount) {
+ final String unreadText = getUnreadText(context);
// Localize "99+" according to the device language
- unreadCountString = String.format(sUnreadText, sMaxUnreadCount);
+ unreadCountString = String.format(unreadText, maxUnreadCount);
} else if (unreadCount <= 0) {
unreadCountString = "";
} else {
@@ -385,52 +437,21 @@
*/
public static String getUnseenCountString(Context context, int unseenCount) {
final String unseenCountString;
- final Resources resources = context.getResources();
- if (sMaxUnreadCount == -1) {
- sMaxUnreadCount = resources.getInteger(R.integer.maxUnreadCount);
- }
- if (unseenCount > sMaxUnreadCount) {
- if (sLargeUnseenText == null) {
- sLargeUnseenText = resources.getString(R.string.large_unseen_count);
- }
+ final int maxUnreadCount = getMaxUnreadCount(context);
+ if (unseenCount > maxUnreadCount) {
+ final String largeUnseenText = getLargeUnseenText(context);
// Localize "99+" according to the device language
- unseenCountString = String.format(sLargeUnseenText, sMaxUnreadCount);
+ unseenCountString = String.format(largeUnseenText, maxUnreadCount);
} else if (unseenCount <= 0) {
unseenCountString = "";
} else {
- if (sUnseenText == null) {
- sUnseenText = resources.getString(R.string.unseen_count);
- }
// Localize unseen count according to the device language
- unseenCountString = String.format(sUnseenText, unseenCount);
+ unseenCountString = String.format(getUnseenText(context), unseenCount);
}
return unseenCountString;
}
/**
- * Get the correct display string for the unread count in the actionbar.
- */
- public static CharSequence getUnreadMessageString(Context context, int unreadCount) {
- final SpannableString message;
- final Resources resources = context.getResources();
- if (sMaxUnreadCount == -1) {
- sMaxUnreadCount = resources.getInteger(R.integer.maxUnreadCount);
- }
- if (unreadCount > sMaxUnreadCount) {
- message = new SpannableString(
- resources.getString(R.string.actionbar_large_unread_count, sMaxUnreadCount));
- } else {
- message = new SpannableString(resources.getQuantityString(
- R.plurals.actionbar_unread_messages, unreadCount, unreadCount));
- }
-
- message.setSpan(CharacterStyle.wrap(ACTION_BAR_UNREAD_STYLE), 0,
- message.toString().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- return message;
- }
-
- /**
* Get text matching the last sync status.
*/
public static CharSequence getSyncStatusText(Context context, int packedStatus) {
@@ -666,15 +687,6 @@
}
/**
- * Retrieves the mailbox search query associated with an intent (or null if not available),
- * doing proper sanitizing (e.g. trims whitespace).
- */
- public static String mailSearchQueryForIntent(Intent intent) {
- String query = intent.getStringExtra(SearchManager.QUERY);
- return TextUtils.isEmpty(query) ? null : query.trim();
- }
-
- /**
* Split out a filename's extension and return it.
* @param filename a file name
* @return the file extension (max of 5 chars including period, like ".docx"), or null
@@ -801,23 +813,6 @@
return sw.toString();
}
- public static void dumpViewTree(ViewGroup root) {
- dumpViewTree(root, "");
- }
-
- private static void dumpViewTree(ViewGroup g, String prefix) {
- LogUtils.i(LOG_TAG, "%sVIEWGROUP: %s childCount=%s", prefix, g, g.getChildCount());
- final String childPrefix = prefix + " ";
- for (int i = 0; i < g.getChildCount(); i++) {
- final View child = g.getChildAt(i);
- if (child instanceof ViewGroup) {
- dumpViewTree((ViewGroup) child, childPrefix);
- } else {
- LogUtils.i(LOG_TAG, "%sCHILD #%s: %s", childPrefix, i, child);
- }
- }
- }
-
/**
* Executes an out-of-band command on the cursor.
* @param cursor
@@ -914,14 +909,6 @@
}
/**
- * This utility method returns the conversation Uri at the current cursor position.
- * @return the conversation id at the cursor.
- */
- public static String getConversationUri(ConversationCursor cursor) {
- return cursor.getString(UIProvider.CONVERSATION_URI_COLUMN);
- }
-
- /**
* @return whether to show two pane or single pane search results.
*/
public static boolean showTwoPaneSearchResults(Context context) {
@@ -939,14 +926,6 @@
}
}
- public static int getDefaultFolderBackgroundColor(Context context) {
- if (sDefaultFolderBackgroundColor == -1) {
- sDefaultFolderBackgroundColor = context.getResources().getColor(
- R.color.default_folder_background_color);
- }
- return sDefaultFolderBackgroundColor;
- }
-
/**
* Returns the count that should be shown for the specified folder. This method should be used
* when the UI wants to display an "unread" count. For most labels, the returned value will be
@@ -966,25 +945,6 @@
return 0;
}
- /**
- * @return an intent which, if launched, will reply to the conversation
- */
- public static Intent createReplyIntent(final Context context, final Account account,
- final Uri messageUri, final boolean isReplyAll) {
- final Intent intent =
- ComposeActivity.createReplyIntent(context, account, messageUri, isReplyAll);
- return intent;
- }
-
- /**
- * @return an intent which, if launched, will forward the conversation
- */
- public static Intent createForwardIntent(
- final Context context, final Account account, final Uri messageUri) {
- final Intent intent = ComposeActivity.createForwardIntent(context, account, messageUri);
- return intent;
- }
-
public static Uri appendVersionQueryParameter(final Context context, final Uri uri) {
return uri.buildUpon().appendQueryParameter(APP_VERSION_QUERY_PARAMETER,
getVersionCode(context)).build();
@@ -1108,7 +1068,6 @@
*/
public static Spanned insertStringWithStyle(Context context,
String entireString, String subString, int appearance) {
- final Resources resources = context.getResources();
final int index = entireString.indexOf(subString);
final SpannableString descriptionText = new SpannableString(entireString);
descriptionText.setSpan(
diff --git a/tests/src/com/android/mail/utils/AdvancedHtmlSanitizerTest.java b/tests/src/com/android/mail/utils/AdvancedHtmlSanitizerTest.java
index 578ce4a..0e5715b 100644
--- a/tests/src/com/android/mail/utils/AdvancedHtmlSanitizerTest.java
+++ b/tests/src/com/android/mail/utils/AdvancedHtmlSanitizerTest.java
@@ -73,7 +73,9 @@
}
/**
- * Technically, RFC 2392 doesn't limit where CID urls may appear; they are accepted everywhere.
+ * Technically, RFC 2392 doesn't limit where CID urls may appear. But, Webview is unhappy
+ * handling them within link tags, so we only allow them in img src attributes until we see a
+ * reason to expand their acceptance.
*/
public void testCIDurls() {
sanitize("<img src=\"http://www.here.com/awesome.png\"/>",
@@ -87,8 +89,7 @@
"<a href=\"http://www.here.com/awesome.png\"></a>");
sanitize("<a href=\"https://www.here.com/awesome.png\"/>",
"<a href=\"https://www.here.com/awesome.png\"></a>");
- sanitize("<a href=\"cid:ii_145bda161daf6f9c\"/>",
- "<a href=\"cid:ii_145bda161daf6f9c\"></a>");
+ sanitize("<a href=\"cid:ii_145bda161daf6f9c\"/>", "");
}
// todo the stock CssSchema in OWASP does NOT allow the float property; I experiment with adding
diff --git a/tests/src/com/android/mail/utils/BasicHtmlSanitizerTest.java b/tests/src/com/android/mail/utils/BasicHtmlSanitizerTest.java
index 1da1ce0..5916162 100644
--- a/tests/src/com/android/mail/utils/BasicHtmlSanitizerTest.java
+++ b/tests/src/com/android/mail/utils/BasicHtmlSanitizerTest.java
@@ -21,6 +21,7 @@
// allowed attributes
sanitize("<a coords=\"something\"></a>", "<a coords=\"something\"></a>");
sanitize("<a href=\"http://www.here.com\"></a>", "<a href=\"http://www.here.com\"></a>");
+ sanitize("<a href=\"https://www.here.com\"></a>", "<a href=\"https://www.here.com\"></a>");
sanitize("<a name=\"something\"></a>", "<a name=\"something\"></a>");
sanitize("<a shape=\"something\"></a>", "<a shape=\"something\"></a>");
@@ -30,6 +31,7 @@
sanitize("<a datasrc=\"something\"></a>", "");
sanitize("<a download=\"something\"></a>", "");
sanitize("<a href=\"javascript:badness()\"></a>", "");
+ sanitize("<a href=\"cid:ii_hyw5v8ej0\"></a>", "");
sanitize("<a hreflang=\"something\"></a>", "");
sanitize("<a media=\"something\"></a>", "");
sanitize("<a methods=\"something\"></a>", "");
@@ -70,6 +72,7 @@
sanitize("<area alt=\"something\"/>", "<area alt=\"something\" />");
sanitize("<area coords=\"something\"/>", "<area coords=\"something\" />");
sanitize("<area href=\"http://www.here.com\"/>", "<area href=\"http://www.here.com\" />");
+ sanitize("<area href=\"https://www.here.com\"/>", "<area href=\"https://www.here.com\" />");
sanitize("<area name=\"something\"/>", "<area name=\"something\" />");
sanitize("<area nohref />", "<area nohref=\"nohref\" />");
sanitize("<area shape=\"something\"/>", "<area shape=\"something\" />");
@@ -78,6 +81,7 @@
sanitize("<area accessKey=\"A\"/>", "<area />");
sanitize("<area download=\"something\"/>", "<area />");
sanitize("<area href=\"javascript:badness()\"/>", "<area />");
+ sanitize("<area href=\"cid:ii_hyw5v8ej0\"/>", "<area />");
sanitize("<area hreflang=\"something\"/>", "<area />");
sanitize("<area media=\"something\"/>", "<area />");
sanitize("<area rel=\"something\"/>", "<area />");
@@ -106,10 +110,13 @@
// allowed attributes
sanitize("<base href=\"http://www.example.com/\">",
"<base href=\"http://www.example.com/\" />");
+ sanitize("<base href=\"https://www.example.com/\">",
+ "<base href=\"https://www.example.com/\" />");
// disallowed attributes
sanitize("<base target=\"_blank\">", "<base />");
sanitize("<base href=\"javascript:badness()\">", "<base />");
+ sanitize("<base href=\"cid:ii_hyw5v8ej0\">", "<base />");
sanitize("<base href=\"javascript:alert('XSS');//\">", "<base />");
}
@@ -485,11 +492,18 @@
sanitize("<img width=\"22\"/>", "<img width=\"22\" />");
sanitize("<img src=\"http://www.overhere.com/\"></img>",
"<img src=\"http://www.overhere.com/\" />");
+ sanitize("<img src=\"https://www.overhere.com/\"></img>",
+ "<img src=\"https://www.overhere.com/\" />");
+ sanitize("<img src=\"cid:ii_hyw5v8ej0\"></img>",
+ "<img src=\"cid:ii_hyw5v8ej0\" />");
sanitize("<img longdesc=\"http://www.overhere.com/\"></img>",
"<img longdesc=\"http://www.overhere.com/\" />");
+ sanitize("<img longdesc=\"https://www.overhere.com/\"></img>",
+ "<img longdesc=\"https://www.overhere.com/\" />");
sanitize("<img src=\"javascript:badness()\"></img>", "");
sanitize("<img longdesc=\"javascript:badness()\"></img>", "");
+ sanitize("<img longdesc=\"cid:ii_hyw5v8ej0\"></img>", "");
sanitize("<img src=javascript:alert('XSS')>", "");
sanitize("<img src=JaVaScRiPt:alert('XSS')>", "");
sanitize("<img src=javascript:alert(\"XSS\")>", "");
@@ -555,10 +569,16 @@
sanitize("<input width=\"50\"/>", "<input width=\"50\" />");
sanitize("<input src=\"http://www.overhere.com/\"></input>",
"<input src=\"http://www.overhere.com/\" />");
+ sanitize("<input src=\"https://www.overhere.com/\"></input>",
+ "<input src=\"https://www.overhere.com/\" />");
sanitize("<input formaction=\"http://www.overhere.com/\"></input>",
"<input formaction=\"http://www.overhere.com/\" />");
+ sanitize("<input formaction=\"https://www.overhere.com/\"></input>",
+ "<input formaction=\"https://www.overhere.com/\" />");
+ sanitize("<input src=\"cid:ii_hyw5v8ej0\"></input>", "");
sanitize("<input src=\"javascript:badness()\"></input>", "");
+ sanitize("<input formaction=\"cid:ii_hyw5v8ej0\"></input>", "");
sanitize("<input formaction=\"javascript:badness()\"></input>", "");
sanitize("<input type=\"image\" src=\"javascript:alert('XSS');\">",
"<input type=\"image\" />");
@@ -575,8 +595,13 @@
sanitize("<ins cite=\"javascript:badness();\">something</ins>", "<ins>something</ins>");
sanitize("<ins cite=\"http://www.reason.com/\">something</ins>",
"<ins cite=\"http://www.reason.com/\">something</ins>");
+ sanitize("<ins cite=\"https://www.reason.com/\">something</ins>",
+ "<ins cite=\"https://www.reason.com/\">something</ins>");
sanitize("<ins datetime=\"something\">something</ins>",
"<ins datetime=\"something\">something</ins>");
+
+ sanitize("<ins cite=\"cid:ii_hyw5v8ej0\">something</ins>",
+ "<ins>something</ins>");
}
public void testKbd() {
@@ -614,6 +639,8 @@
public void testLink() {
sanitize("<link charset=\"utf8\"/>", "");
sanitize("<link href=\"http://www.reason.com/\"/>", "");
+ sanitize("<link href=\"https://www.reason.com/\"/>", "");
+ sanitize("<link href=\"cid:ii_hyw5v8ej0\"/>", "");
sanitize("<link hreflang=\"fr_CA\"/>", "");
sanitize("<link media=\"tv\"/>", "");
sanitize("<link rel=\"alternate\"/>", "");
@@ -655,6 +682,8 @@
"<menuitem disabled=\"disabled\"></menuitem>");
sanitize("<menuitem icon=\"http://www.reason.com/\"></menuitem>",
"<menuitem icon=\"http://www.reason.com/\"></menuitem>");
+ sanitize("<menuitem icon=\"https://www.reason.com/\"></menuitem>",
+ "<menuitem icon=\"https://www.reason.com/\"></menuitem>");
sanitize("<menuitem label=\"something\"></menuitem>",
"<menuitem label=\"something\"></menuitem>");
sanitize("<menuitem type=\"checkbox\"></menuitem>",
@@ -662,6 +691,7 @@
sanitize("<menuitem radiogroup=\"something\"></menuitem>",
"<menuitem radiogroup=\"something\"></menuitem>");
+ sanitize("<menuitem icon=\"cid:ii_hyw5v8ej0\"></menuitem>", "<menuitem></menuitem>");
sanitize("<menuitem icon=\"javascript:badness()\"></menuitem>", "<menuitem></menuitem>");
}
@@ -773,6 +803,10 @@
sanitize("<q>something</q>", "<q>something</q>");
sanitize("<q cite=\"http://www.reason.com/\">something</q>",
"<q cite=\"http://www.reason.com/\">something</q>");
+ sanitize("<q cite=\"https://www.reason.com/\">something</q>",
+ "<q cite=\"https://www.reason.com/\">something</q>");
+
+ sanitize("<q cite=\"cid:ii_hyw5v8ej0\">something</q>", "<q>something</q>");
sanitize("<q cite=\"javascript:badness()\">something</q>", "<q>something</q>");
}