Merge "Disable from-text stub generation for i18n.module.intra.core.api" into main
diff --git a/Android.bp b/Android.bp
index 8abda73..39811ec 100644
--- a/Android.bp
+++ b/Android.bp
@@ -41,11 +41,11 @@
"SPDX-license-identifier-ICU",
"SPDX-license-identifier-ISC",
"SPDX-license-identifier-MIT",
- "SPDX-license-identifier-Unicode-DFS",
+ "SPDX-license-identifier-Unicode-3.0",
"legacy_unencumbered",
],
license_text: [
- "NOTICE",
+ "LICENSE",
],
}
@@ -54,7 +54,7 @@
visibility: [
"//external/icu:__subpackages__",
],
- srcs: ["NOTICE"],
+ srcs: ["LICENSE"],
}
cc_defaults {
@@ -101,6 +101,7 @@
libs: [
"fake_device_config",
"aconfig-annotations-lib-sdk-none",
+ "aconfig_storage_reader_java_none",
"unsupportedappusage-sdk-none",
],
apex_available: [
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6fdb4aa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,717 @@
+UNICODE LICENSE V3
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright © 2016-2023 Unicode, Inc.
+
+NOTICE TO USER: Carefully read the following legal agreement. BY
+DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
+SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
+TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
+DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of data files and any associated documentation (the "Data Files") or
+software and any associated documentation (the "Software") to deal in the
+Data Files or Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Data Files or Software, and to permit persons to whom the
+Data Files or Software are furnished to do so, provided that either (a)
+this copyright and permission notice appear with all copies of the Data
+Files or Software, or (b) this copyright and permission notice appear in
+associated Documentation.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
+THIRD PARTY RIGHTS.
+
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
+BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
+FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall
+not be used in advertising or otherwise to promote the sale, use or other
+dealings in these Data Files or Software without prior written
+authorization of the copyright holder.
+
+----------------------------------------------------------------------
+
+Third-Party Software Licenses
+
+This section contains third-party software notices and/or additional
+terms for licensed third-party software components included within ICU
+libraries.
+
+----------------------------------------------------------------------
+
+ICU License - ICU 1.8.1 to ICU 57.1
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1995-2016 International Business Machines Corporation and others
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, and/or sell copies of the Software, and to permit persons
+to whom the Software is furnished to do so, provided that the above
+copyright notice(s) and this permission notice appear in all copies of
+the Software and that both the above copyright notice(s) and this
+permission notice appear in supporting documentation.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
+SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale, use
+or other dealings in this Software without prior written authorization
+of the copyright holder.
+
+All trademarks and registered trademarks mentioned herein are the
+property of their respective owners.
+
+----------------------------------------------------------------------
+
+Chinese/Japanese Word Break Dictionary Data (cjdict.txt)
+
+ # The Google Chrome software developed by Google is licensed under
+ # the BSD license. Other software included in this distribution is
+ # provided under other licenses, as set forth below.
+ #
+ # The BSD License
+ # http://opensource.org/licenses/bsd-license.php
+ # Copyright (C) 2006-2008, Google Inc.
+ #
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions are met:
+ #
+ # Redistributions of source code must retain the above copyright notice,
+ # this list of conditions and the following disclaimer.
+ # Redistributions in binary form must reproduce the above
+ # copyright notice, this list of conditions and the following
+ # disclaimer in the documentation and/or other materials provided with
+ # the distribution.
+ # Neither the name of Google Inc. nor the names of its
+ # contributors may be used to endorse or promote products derived from
+ # this software without specific prior written permission.
+ #
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ #
+ #
+ # The word list in cjdict.txt are generated by combining three word lists
+ # listed below with further processing for compound word breaking. The
+ # frequency is generated with an iterative training against Google web
+ # corpora.
+ #
+ # * Libtabe (Chinese)
+ # - https://sourceforge.net/project/?group_id=1519
+ # - Its license terms and conditions are shown below.
+ #
+ # * IPADIC (Japanese)
+ # - http://chasen.aist-nara.ac.jp/chasen/distribution.html
+ # - Its license terms and conditions are shown below.
+ #
+ # ---------COPYING.libtabe ---- BEGIN--------------------
+ #
+ # /*
+ # * Copyright (c) 1999 TaBE Project.
+ # * Copyright (c) 1999 Pai-Hsiang Hsiao.
+ # * All rights reserved.
+ # *
+ # * Redistribution and use in source and binary forms, with or without
+ # * modification, are permitted provided that the following conditions
+ # * are met:
+ # *
+ # * . Redistributions of source code must retain the above copyright
+ # * notice, this list of conditions and the following disclaimer.
+ # * . Redistributions in binary form must reproduce the above copyright
+ # * notice, this list of conditions and the following disclaimer in
+ # * the documentation and/or other materials provided with the
+ # * distribution.
+ # * . Neither the name of the TaBE Project nor the names of its
+ # * contributors may be used to endorse or promote products derived
+ # * from this software without specific prior written permission.
+ # *
+ # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # * OF THE POSSIBILITY OF SUCH DAMAGE.
+ # */
+ #
+ # /*
+ # * Copyright (c) 1999 Computer Systems and Communication Lab,
+ # * Institute of Information Science, Academia
+ # * Sinica. All rights reserved.
+ # *
+ # * Redistribution and use in source and binary forms, with or without
+ # * modification, are permitted provided that the following conditions
+ # * are met:
+ # *
+ # * . Redistributions of source code must retain the above copyright
+ # * notice, this list of conditions and the following disclaimer.
+ # * . Redistributions in binary form must reproduce the above copyright
+ # * notice, this list of conditions and the following disclaimer in
+ # * the documentation and/or other materials provided with the
+ # * distribution.
+ # * . Neither the name of the Computer Systems and Communication Lab
+ # * nor the names of its contributors may be used to endorse or
+ # * promote products derived from this software without specific
+ # * prior written permission.
+ # *
+ # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # * OF THE POSSIBILITY OF SUCH DAMAGE.
+ # */
+ #
+ # Copyright 1996 Chih-Hao Tsai @ Beckman Institute,
+ # University of Illinois
+ # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4
+ #
+ # ---------------COPYING.libtabe-----END--------------------------------
+ #
+ #
+ # ---------------COPYING.ipadic-----BEGIN-------------------------------
+ #
+ # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science
+ # and Technology. All Rights Reserved.
+ #
+ # Use, reproduction, and distribution of this software is permitted.
+ # Any copy of this software, whether in its original form or modified,
+ # must include both the above copyright notice and the following
+ # paragraphs.
+ #
+ # Nara Institute of Science and Technology (NAIST),
+ # the copyright holders, disclaims all warranties with regard to this
+ # software, including all implied warranties of merchantability and
+ # fitness, in no event shall NAIST be liable for
+ # any special, indirect or consequential damages or any damages
+ # whatsoever resulting from loss of use, data or profits, whether in an
+ # action of contract, negligence or other tortuous action, arising out
+ # of or in connection with the use or performance of this software.
+ #
+ # A large portion of the dictionary entries
+ # originate from ICOT Free Software. The following conditions for ICOT
+ # Free Software applies to the current dictionary as well.
+ #
+ # Each User may also freely distribute the Program, whether in its
+ # original form or modified, to any third party or parties, PROVIDED
+ # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear
+ # on, or be attached to, the Program, which is distributed substantially
+ # in the same form as set out herein and that such intended
+ # distribution, if actually made, will neither violate or otherwise
+ # contravene any of the laws and regulations of the countries having
+ # jurisdiction over the User or the intended distribution itself.
+ #
+ # NO WARRANTY
+ #
+ # The program was produced on an experimental basis in the course of the
+ # research and development conducted during the project and is provided
+ # to users as so produced on an experimental basis. Accordingly, the
+ # program is provided without any warranty whatsoever, whether express,
+ # implied, statutory or otherwise. The term "warranty" used herein
+ # includes, but is not limited to, any warranty of the quality,
+ # performance, merchantability and fitness for a particular purpose of
+ # the program and the nonexistence of any infringement or violation of
+ # any right of any third party.
+ #
+ # Each user of the program will agree and understand, and be deemed to
+ # have agreed and understood, that there is no warranty whatsoever for
+ # the program and, accordingly, the entire risk arising from or
+ # otherwise connected with the program is assumed by the user.
+ #
+ # Therefore, neither ICOT, the copyright holder, or any other
+ # organization that participated in or was otherwise related to the
+ # development of the program and their respective officials, directors,
+ # officers and other employees shall be held liable for any and all
+ # damages, including, without limitation, general, special, incidental
+ # and consequential damages, arising out of or otherwise in connection
+ # with the use or inability to use the program or any product, material
+ # or result produced or otherwise obtained by using the program,
+ # regardless of whether they have been advised of, or otherwise had
+ # knowledge of, the possibility of such damages at any time during the
+ # project or thereafter. Each user will be deemed to have agreed to the
+ # foregoing by his or her commencement of use of the program. The term
+ # "use" as used herein includes, but is not limited to, the use,
+ # modification, copying and distribution of the program and the
+ # production of secondary products from the program.
+ #
+ # In the case where the program, whether in its original form or
+ # modified, was distributed or delivered to or received by a user from
+ # any person, organization or entity other than ICOT, unless it makes or
+ # grants independently of ICOT any specific warranty to the user in
+ # writing, such person, organization or entity, will also be exempted
+ # from and not be held liable to the user for any such damages as noted
+ # above as far as the program is concerned.
+ #
+ # ---------------COPYING.ipadic-----END----------------------------------
+
+----------------------------------------------------------------------
+
+Lao Word Break Dictionary Data (laodict.txt)
+
+ # Copyright (C) 2016 and later: Unicode, Inc. and others.
+ # License & terms of use: http://www.unicode.org/copyright.html
+ # Copyright (c) 2015 International Business Machines Corporation
+ # and others. All Rights Reserved.
+ #
+ # Project: https://github.com/rober42539/lao-dictionary
+ # Dictionary: https://github.com/rober42539/lao-dictionary/laodict.txt
+ # License: https://github.com/rober42539/lao-dictionary/LICENSE.txt
+ # (copied below)
+ #
+ # This file is derived from the above dictionary version of Nov 22, 2020
+ # ----------------------------------------------------------------------
+ # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell.
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions are met:
+ #
+ # Redistributions of source code must retain the above copyright notice, this
+ # list of conditions and the following disclaimer. Redistributions in binary
+ # form must reproduce the above copyright notice, this list of conditions and
+ # the following disclaimer in the documentation and/or other materials
+ # provided with the distribution.
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
+ # --------------------------------------------------------------------------
+
+----------------------------------------------------------------------
+
+Burmese Word Break Dictionary Data (burmesedict.txt)
+
+ # Copyright (c) 2014 International Business Machines Corporation
+ # and others. All Rights Reserved.
+ #
+ # This list is part of a project hosted at:
+ # github.com/kanyawtech/myanmar-karen-word-lists
+ #
+ # --------------------------------------------------------------------------
+ # Copyright (c) 2013, LeRoy Benjamin Sharon
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions
+ # are met: Redistributions of source code must retain the above
+ # copyright notice, this list of conditions and the following
+ # disclaimer. Redistributions in binary form must reproduce the
+ # above copyright notice, this list of conditions and the following
+ # disclaimer in the documentation and/or other materials provided
+ # with the distribution.
+ #
+ # Neither the name Myanmar Karen Word Lists, nor the names of its
+ # contributors may be used to endorse or promote products derived
+ # from this software without specific prior written permission.
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ # SUCH DAMAGE.
+ # --------------------------------------------------------------------------
+
+----------------------------------------------------------------------
+
+Time Zone Database
+
+ ICU uses the public domain data and code derived from Time Zone
+Database for its time zone support. The ownership of the TZ database
+is explained in BCP 175: Procedure for Maintaining the Time Zone
+Database section 7.
+
+ # 7. Database Ownership
+ #
+ # The TZ database itself is not an IETF Contribution or an IETF
+ # document. Rather it is a pre-existing and regularly updated work
+ # that is in the public domain, and is intended to remain in the
+ # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do
+ # not apply to the TZ Database or contributions that individuals make
+ # to it. Should any claims be made and substantiated against the TZ
+ # Database, the organization that is providing the IANA
+ # Considerations defined in this RFC, under the memorandum of
+ # understanding with the IETF, currently ICANN, may act in accordance
+ # with all competent court orders. No ownership claims will be made
+ # by ICANN or the IETF Trust on the database or the code. Any person
+ # making a contribution to the database or code waives all rights to
+ # future claims in that contribution or in the TZ Database.
+
+----------------------------------------------------------------------
+
+Google double-conversion
+
+Copyright 2006-2011, the V8 project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+
+File: aclocal.m4 (only for ICU4C)
+Section: pkg.m4 - Macros to locate and utilise pkg-config.
+
+
+Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
+Copyright © 2012-2015 Dan Nicholson <dbn.lists@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+02111-1307, USA.
+
+As a special exception to the GNU General Public License, if you
+distribute this file as part of a program that contains a
+configuration script generated by Autoconf, you may include it under
+the same distribution terms that you use for the rest of that
+program.
+
+
+(The condition for the exception is fulfilled because
+ICU4C includes a configuration script generated by Autoconf,
+namely the `configure` script.)
+
+----------------------------------------------------------------------
+
+File: config.guess (only for ICU4C)
+
+
+This file is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses/>.
+
+As a special exception to the GNU General Public License, if you
+distribute this file as part of a program that contains a
+configuration script generated by Autoconf, you may include it under
+the same distribution terms that you use for the rest of that
+program. This Exception is an additional permission under section 7
+of the GNU General Public License, version 3 ("GPLv3").
+
+
+(The condition for the exception is fulfilled because
+ICU4C includes a configuration script generated by Autoconf,
+namely the `configure` script.)
+
+----------------------------------------------------------------------
+
+File: install-sh (only for ICU4C)
+
+
+Copyright 1991 by the Massachusetts Institute of Technology
+
+Permission to use, copy, modify, distribute, and sell this software and its
+documentation for any purpose is hereby granted without fee, provided that
+the above copyright notice appear in all copies and that both that
+copyright notice and this permission notice appear in supporting
+documentation, and that the name of M.I.T. not be used in advertising or
+publicity pertaining to distribution of the software without specific,
+written prior permission. M.I.T. makes no representations about the
+suitability of this software for any purpose. It is provided "as is"
+without express or implied warranty.
+
+----------------------------------------------------------------------
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2024 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.
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 25b6eb9..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,414 +0,0 @@
-COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later)
-
-Copyright © 1991-2018 Unicode, Inc. All rights reserved.
-Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Unicode data files and any associated documentation
-(the "Data Files") or Unicode software and any associated documentation
-(the "Software") to deal in the Data Files or Software
-without restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, and/or sell copies of
-the Data Files or Software, and to permit persons to whom the Data Files
-or Software are furnished to do so, provided that either
-(a) this copyright and permission notice appear with all copies
-of the Data Files or Software, or
-(b) this copyright and permission notice appear in associated
-Documentation.
-
-THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
-ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
-NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
-DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
-DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
-TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-PERFORMANCE OF THE DATA FILES OR SOFTWARE.
-
-Except as contained in this notice, the name of a copyright holder
-shall not be used in advertising or otherwise to promote the sale,
-use or other dealings in these Data Files or Software without prior
-written authorization of the copyright holder.
-
----------------------
-
-Third-Party Software Licenses
-
-This section contains third-party software notices and/or additional
-terms for licensed third-party software components included within ICU
-libraries.
-
-1. ICU License - ICU 1.8.1 to ICU 57.1
-
-COPYRIGHT AND PERMISSION NOTICE
-
-Copyright (c) 1995-2016 International Business Machines Corporation and others
-All rights reserved.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, and/or sell copies of the Software, and to permit persons
-to whom the Software is furnished to do so, provided that the above
-copyright notice(s) and this permission notice appear in all copies of
-the Software and that both the above copyright notice(s) and this
-permission notice appear in supporting documentation.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
-SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
-RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
-CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-Except as contained in this notice, the name of a copyright holder
-shall not be used in advertising or otherwise to promote the sale, use
-or other dealings in this Software without prior written authorization
-of the copyright holder.
-
-All trademarks and registered trademarks mentioned herein are the
-property of their respective owners.
-
-2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt)
-
- # The Google Chrome software developed by Google is licensed under
- # the BSD license. Other software included in this distribution is
- # provided under other licenses, as set forth below.
- #
- # The BSD License
- # http://opensource.org/licenses/bsd-license.php
- # Copyright (C) 2006-2008, Google Inc.
- #
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are met:
- #
- # Redistributions of source code must retain the above copyright notice,
- # this list of conditions and the following disclaimer.
- # Redistributions in binary form must reproduce the above
- # copyright notice, this list of conditions and the following
- # disclaimer in the documentation and/or other materials provided with
- # the distribution.
- # Neither the name of Google Inc. nor the names of its
- # contributors may be used to endorse or promote products derived from
- # this software without specific prior written permission.
- #
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- #
- #
- # The word list in cjdict.txt are generated by combining three word lists
- # listed below with further processing for compound word breaking. The
- # frequency is generated with an iterative training against Google web
- # corpora.
- #
- # * Libtabe (Chinese)
- # - https://sourceforge.net/project/?group_id=1519
- # - Its license terms and conditions are shown below.
- #
- # * IPADIC (Japanese)
- # - http://chasen.aist-nara.ac.jp/chasen/distribution.html
- # - Its license terms and conditions are shown below.
- #
- # ---------COPYING.libtabe ---- BEGIN--------------------
- #
- # /*
- # * Copyright (c) 1999 TaBE Project.
- # * Copyright (c) 1999 Pai-Hsiang Hsiao.
- # * All rights reserved.
- # *
- # * Redistribution and use in source and binary forms, with or without
- # * modification, are permitted provided that the following conditions
- # * are met:
- # *
- # * . Redistributions of source code must retain the above copyright
- # * notice, this list of conditions and the following disclaimer.
- # * . Redistributions in binary form must reproduce the above copyright
- # * notice, this list of conditions and the following disclaimer in
- # * the documentation and/or other materials provided with the
- # * distribution.
- # * . Neither the name of the TaBE Project nor the names of its
- # * contributors may be used to endorse or promote products derived
- # * from this software without specific prior written permission.
- # *
- # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- # * OF THE POSSIBILITY OF SUCH DAMAGE.
- # */
- #
- # /*
- # * Copyright (c) 1999 Computer Systems and Communication Lab,
- # * Institute of Information Science, Academia
- # * Sinica. All rights reserved.
- # *
- # * Redistribution and use in source and binary forms, with or without
- # * modification, are permitted provided that the following conditions
- # * are met:
- # *
- # * . Redistributions of source code must retain the above copyright
- # * notice, this list of conditions and the following disclaimer.
- # * . Redistributions in binary form must reproduce the above copyright
- # * notice, this list of conditions and the following disclaimer in
- # * the documentation and/or other materials provided with the
- # * distribution.
- # * . Neither the name of the Computer Systems and Communication Lab
- # * nor the names of its contributors may be used to endorse or
- # * promote products derived from this software without specific
- # * prior written permission.
- # *
- # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- # * OF THE POSSIBILITY OF SUCH DAMAGE.
- # */
- #
- # Copyright 1996 Chih-Hao Tsai @ Beckman Institute,
- # University of Illinois
- # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4
- #
- # ---------------COPYING.libtabe-----END--------------------------------
- #
- #
- # ---------------COPYING.ipadic-----BEGIN-------------------------------
- #
- # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science
- # and Technology. All Rights Reserved.
- #
- # Use, reproduction, and distribution of this software is permitted.
- # Any copy of this software, whether in its original form or modified,
- # must include both the above copyright notice and the following
- # paragraphs.
- #
- # Nara Institute of Science and Technology (NAIST),
- # the copyright holders, disclaims all warranties with regard to this
- # software, including all implied warranties of merchantability and
- # fitness, in no event shall NAIST be liable for
- # any special, indirect or consequential damages or any damages
- # whatsoever resulting from loss of use, data or profits, whether in an
- # action of contract, negligence or other tortuous action, arising out
- # of or in connection with the use or performance of this software.
- #
- # A large portion of the dictionary entries
- # originate from ICOT Free Software. The following conditions for ICOT
- # Free Software applies to the current dictionary as well.
- #
- # Each User may also freely distribute the Program, whether in its
- # original form or modified, to any third party or parties, PROVIDED
- # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear
- # on, or be attached to, the Program, which is distributed substantially
- # in the same form as set out herein and that such intended
- # distribution, if actually made, will neither violate or otherwise
- # contravene any of the laws and regulations of the countries having
- # jurisdiction over the User or the intended distribution itself.
- #
- # NO WARRANTY
- #
- # The program was produced on an experimental basis in the course of the
- # research and development conducted during the project and is provided
- # to users as so produced on an experimental basis. Accordingly, the
- # program is provided without any warranty whatsoever, whether express,
- # implied, statutory or otherwise. The term "warranty" used herein
- # includes, but is not limited to, any warranty of the quality,
- # performance, merchantability and fitness for a particular purpose of
- # the program and the nonexistence of any infringement or violation of
- # any right of any third party.
- #
- # Each user of the program will agree and understand, and be deemed to
- # have agreed and understood, that there is no warranty whatsoever for
- # the program and, accordingly, the entire risk arising from or
- # otherwise connected with the program is assumed by the user.
- #
- # Therefore, neither ICOT, the copyright holder, or any other
- # organization that participated in or was otherwise related to the
- # development of the program and their respective officials, directors,
- # officers and other employees shall be held liable for any and all
- # damages, including, without limitation, general, special, incidental
- # and consequential damages, arising out of or otherwise in connection
- # with the use or inability to use the program or any product, material
- # or result produced or otherwise obtained by using the program,
- # regardless of whether they have been advised of, or otherwise had
- # knowledge of, the possibility of such damages at any time during the
- # project or thereafter. Each user will be deemed to have agreed to the
- # foregoing by his or her commencement of use of the program. The term
- # "use" as used herein includes, but is not limited to, the use,
- # modification, copying and distribution of the program and the
- # production of secondary products from the program.
- #
- # In the case where the program, whether in its original form or
- # modified, was distributed or delivered to or received by a user from
- # any person, organization or entity other than ICOT, unless it makes or
- # grants independently of ICOT any specific warranty to the user in
- # writing, such person, organization or entity, will also be exempted
- # from and not be held liable to the user for any such damages as noted
- # above as far as the program is concerned.
- #
- # ---------------COPYING.ipadic-----END----------------------------------
-
-3. Lao Word Break Dictionary Data (laodict.txt)
-
- # Copyright (c) 2013 International Business Machines Corporation
- # and others. All Rights Reserved.
- #
- # Project: http://code.google.com/p/lao-dictionary/
- # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt
- # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt
- # (copied below)
- #
- # This file is derived from the above dictionary, with slight
- # modifications.
- # ----------------------------------------------------------------------
- # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell.
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification,
- # are permitted provided that the following conditions are met:
- #
- #
- # Redistributions of source code must retain the above copyright notice, this
- # list of conditions and the following disclaimer. Redistributions in
- # binary form must reproduce the above copyright notice, this list of
- # conditions and the following disclaimer in the documentation and/or
- # other materials provided with the distribution.
- #
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- # OF THE POSSIBILITY OF SUCH DAMAGE.
- # --------------------------------------------------------------------------
-
-4. Burmese Word Break Dictionary Data (burmesedict.txt)
-
- # Copyright (c) 2014 International Business Machines Corporation
- # and others. All Rights Reserved.
- #
- # This list is part of a project hosted at:
- # github.com/kanyawtech/myanmar-karen-word-lists
- #
- # --------------------------------------------------------------------------
- # Copyright (c) 2013, LeRoy Benjamin Sharon
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions
- # are met: Redistributions of source code must retain the above
- # copyright notice, this list of conditions and the following
- # disclaimer. Redistributions in binary form must reproduce the
- # above copyright notice, this list of conditions and the following
- # disclaimer in the documentation and/or other materials provided
- # with the distribution.
- #
- # Neither the name Myanmar Karen Word Lists, nor the names of its
- # contributors may be used to endorse or promote products derived
- # from this software without specific prior written permission.
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
- # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- # SUCH DAMAGE.
- # --------------------------------------------------------------------------
-
-5. Time Zone Database
-
- ICU uses the public domain data and code derived from Time Zone
-Database for its time zone support. The ownership of the TZ database
-is explained in BCP 175: Procedure for Maintaining the Time Zone
-Database section 7.
-
- # 7. Database Ownership
- #
- # The TZ database itself is not an IETF Contribution or an IETF
- # document. Rather it is a pre-existing and regularly updated work
- # that is in the public domain, and is intended to remain in the
- # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do
- # not apply to the TZ Database or contributions that individuals make
- # to it. Should any claims be made and substantiated against the TZ
- # Database, the organization that is providing the IANA
- # Considerations defined in this RFC, under the memorandum of
- # understanding with the IETF, currently ICANN, may act in accordance
- # with all competent court orders. No ownership claims will be made
- # by ICANN or the IETF Trust on the database or the code. Any person
- # making a contribution to the database or code waives all rights to
- # future claims in that contribution or in the TZ Database.
-
-6. Google double-conversion
-
-Copyright 2006-2011, the V8 project authors. All rights reserved.
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials provided
- with the distribution.
- * Neither the name of Google Inc. nor the names of its
- contributors may be used to endorse or promote products derived
- from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/OWNERS b/OWNERS
index 2a49be3..ec19bb4 100644
--- a/OWNERS
+++ b/OWNERS
@@ -10,3 +10,5 @@
# g2.corp.android-icu-maintainers@google.com
# android-libcore-team+review@google.com
+per-file *ravenwood* = file:platform/frameworks/base:/ravenwood/OWNERS
+per-file *Ravenwood* = file:platform/frameworks/base:/ravenwood/OWNERS
diff --git a/README-ravenwood.md b/README-ravenwood.md
new file mode 100644
index 0000000..bfc6b62
--- /dev/null
+++ b/README-ravenwood.md
@@ -0,0 +1,31 @@
+# ICU on Ravenwood
+
+# What APIs are enabled
+As of 2024-06-19, Ravenwood uses the host side JVM, not ART, so it doesn't use `libcore` either.
+
+To support ICU on Ravenwood, we include the following jar files in the
+Ravenwood classpath.
+- `core-icu4j-for-host.ravenwood`
+- `icu4j-icudata-jarjar`
+- `icu4j-icutzdata-jarjar`
+
+`core-icu4j-for-host.ravenwood` is made from `core-icu4j-for-host.ravenwood`
+with `hoststubgen` to make the following modifications.
+- Enable `android.icu` APIs on Ravenwood.
+- But all other APIs -- i.e. all `libcore_bridge` APIS -- will throw at runtime.
+
+This "policy" is defined in android_icu4j/icu-ravenwood-policies.txt.
+
+As a result, on Ravenwood, all `android.icu` APIs will work, but none of the `libcore_bridge` APIs.
+
+# CTS
+
+ICU's CTS is `CtsIcuTestCases`, which contains the tests under
+android_icu4j/src/main/tests/, which are the tests from the upstream ICU, and
+android_icu4j/testing/, which are android specific tests, which depends
+on `libcore_bridge`.
+
+On Ravenwood, android_icu4j/src/main/tests/ will pass, but not android_icu4j/testing/.
+
+So we have `CtsIcuTestCasesRavenwood-core-only`, which only contains the
+tests from the upstream. You can run this with `atest CtsIcuTestCasesRavenwood-core-only`.
diff --git a/README.version b/README.version
index 3939e1b..1832b36 100644
--- a/README.version
+++ b/README.version
@@ -1,3 +1,3 @@
URL: https://github.com/unicode-org/icu
-Version: 72.1
+Version: 75.1
BugComponent: 23970
diff --git a/TEST_MAPPING b/TEST_MAPPING
index de82f9f..30e6eb8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -6,5 +6,10 @@
{
"name": "minikin_tests"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsIcuTestCasesRavenwood"
+ }
]
}
diff --git a/android_icu4j/Android.bp b/android_icu4j/Android.bp
index 88ecd4c..5818b50 100644
--- a/android_icu4j/Android.bp
+++ b/android_icu4j/Android.bp
@@ -30,6 +30,10 @@
default_applicable_licenses: ["external_icu_license"],
}
+build = [
+ "Ravenwood.bp",
+]
+
//==========================================================
// build repackaged ICU for target
//
@@ -128,6 +132,7 @@
},
lint: {
warning_checks: ["SuspiciousIndentation"],
+ baseline_filename: "lint-baseline.xml",
},
}
@@ -162,7 +167,7 @@
dxflags: ["--core-library"],
}
-// Java library for use on host, e.g. by robolectric.
+// Java library for use on host, e.g. by ravenwood, robolectric.
java_library {
name: "core-icu4j-for-host",
visibility: [
@@ -170,6 +175,7 @@
"//external/robolectric",
"//external/robolectric-shadows",
"//frameworks/layoutlib",
+ "//frameworks/base/ravenwood:__subpackages__",
"//packages/modules/RuntimeI18n/apex",
],
static_libs: [
@@ -301,7 +307,7 @@
// Referenced implicitly from i18n.module.intra.core.api.
filegroup {
- name: "i18n.module.intra.core.api.api.public.latest",
+ name: "i18n.module.intra.core.api.api.combined.public.latest",
srcs: [
"api/intra/last-api.txt",
],
@@ -309,7 +315,7 @@
// Referenced implicitly from i18n.module.intra.core.api.
filegroup {
- name: "i18n.module.intra.core.api-removed.api.public.latest",
+ name: "i18n.module.intra.core.api-removed.api.combined.public.latest",
srcs: [
"api/intra/last-removed.txt",
],
@@ -325,7 +331,7 @@
// Referenced implicitly from legacy.i18n.module.platform.api.
filegroup {
- name: "legacy.i18n.module.platform.api.api.public.latest",
+ name: "legacy.i18n.module.platform.api.api.combined.public.latest",
srcs: [
"api/legacy_platform/last-api.txt",
],
@@ -333,7 +339,7 @@
// Referenced implicitly from legacy.i18n.module.platform.api.
filegroup {
- name: "legacy.i18n.module.platform.api-removed.api.public.latest",
+ name: "legacy.i18n.module.platform.api-removed.api.combined.public.latest",
srcs: [
"api/legacy_platform/last-removed.txt",
],
@@ -349,7 +355,7 @@
// Referenced implicitly from stable.i18n.module.platform.api.
filegroup {
- name: "stable.i18n.module.platform.api.api.public.latest",
+ name: "stable.i18n.module.platform.api.api.combined.public.latest",
srcs: [
"api/stable_platform/last-api.txt",
],
@@ -357,7 +363,7 @@
// Referenced implicitly from stable.i18n.module.platform.api.
filegroup {
- name: "stable.i18n.module.platform.api-removed.api.public.latest",
+ name: "stable.i18n.module.platform.api-removed.api.combined.public.latest",
srcs: [
"api/stable_platform/last-removed.txt",
],
@@ -456,16 +462,11 @@
// repackaged android.icu classes and methods and not just the ones available
// through the Android API.
//==========================================================
-java_test {
- name: "android-icu4j-tests",
+java_defaults {
+ name: "android-icu4j-tests-default",
visibility: [
"//cts/tests/tests/icu",
],
-
- srcs: [
- "src/main/tests/**/*.java",
- "testing/src/**/*.java",
- ],
java_resource_dirs: [
"src/main/tests",
"testing/src",
@@ -474,6 +475,7 @@
"core-icu4j",
],
static_libs: [
+ "gson",
"junit",
"junit-params",
"tzdata-testing",
@@ -496,3 +498,39 @@
],
},
}
+
+java_test {
+ name: "android-icu4j-tests",
+ defaults: ["android-icu4j-tests-default"],
+ visibility: [
+ "//cts/tests/tests/icu",
+ ],
+ srcs: [
+ "src/main/tests/**/*.java",
+ "testing/src/**/*.java",
+ ],
+}
+
+// Equivalent to android-icu4j-tests, excluding the tests under testing/.
+// We run this as ICU CTS on Ravenwood, where the testing/ tests won't pass due to lack of
+// libcore.
+java_test {
+ name: "android-icu4j-tests-core-only",
+ defaults: ["android-icu4j-tests-default"],
+ visibility: [
+ "//cts/tests/tests/icu",
+ ],
+ srcs: [
+ "src/main/tests/**/*.java",
+ "testing/src/android/icu/testsharding/**/*.java",
+ ],
+ // TODO(b/340889954) Un-excluide the excluded tests.
+ exclude_srcs: [
+ // This class has a "known-failure", which we can't exclude on Ravenwood without
+ // modifying this file, so let's just exclude the whole class for now.
+ "src/main/tests/android/icu/dev/test/format/NumberFormatRegressionTest.java",
+
+ // This test takes too much time and hits the timeout.
+ "src/main/tests/android/icu/dev/test/rbbi/RBBIMonkeyTest.java",
+ ],
+}
diff --git a/android_icu4j/OWNERS b/android_icu4j/OWNERS
new file mode 100644
index 0000000..cb5dcc2
--- /dev/null
+++ b/android_icu4j/OWNERS
@@ -0,0 +1,2 @@
+per-file *ravenwood* = file:platform/frameworks/base:/ravenwood/OWNERS
+per-file *Ravenwood* = file:platform/frameworks/base:/ravenwood/OWNERS
diff --git a/android_icu4j/Ravenwood.bp b/android_icu4j/Ravenwood.bp
new file mode 100644
index 0000000..2839392
--- /dev/null
+++ b/android_icu4j/Ravenwood.bp
@@ -0,0 +1,69 @@
+// Copyright (C) 2024 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.
+
+// For ravenwood.
+// TODO(b/340889954) Enable --supported-api-list-file, once AOSP gets this feature.
+java_genrule {
+ name: "core-icu4j-for-host.ravenwood-base",
+ tools: ["hoststubgen"],
+ cmd: "$(location hoststubgen) " +
+ "@$(location :ravenwood-standard-options) " +
+
+ "--debug-log $(location hoststubgen_core-icu4j-for-host.log) " +
+ "--stats-file $(location hoststubgen_core-icu4j-for-host_stats.csv) " +
+ // "--supported-api-list-file $(location hoststubgen_core-icu4j-for-host_apis.csv) " +
+
+ "--out-impl-jar $(location ravenwood.jar) " +
+
+ "--gen-keep-all-file $(location hoststubgen_core-icu4j-for-host_keep_all.txt) " +
+ "--gen-input-dump-file $(location hoststubgen_core-icu4j-for-host_dump.txt) " +
+
+ "--in-jar $(location :core-icu4j-for-host) " +
+ "--policy-override-file $(location icu-ravenwood-policies.txt) " +
+ "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ",
+ srcs: [
+ ":core-icu4j-for-host",
+
+ "icu-ravenwood-policies.txt",
+ ":ravenwood-standard-options",
+ ":ravenwood-annotation-allowed-classes",
+ ],
+ out: [
+ "ravenwood.jar",
+
+ // Following files are created just as FYI.
+ "hoststubgen_core-icu4j-for-host_keep_all.txt",
+ "hoststubgen_core-icu4j-for-host_dump.txt",
+
+ "hoststubgen_core-icu4j-for-host.log",
+ "hoststubgen_core-icu4j-for-host_stats.csv",
+ // "hoststubgen_core-icu4j-for-host_apis.csv",
+ ],
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+}
+
+// Extract the impl jar from "core-icu4j-for-host.ravenwood-base" for subsequent build rules.
+// Note this emits a "device side" output, so that ravenwood tests can (implicitly)
+// depend on it.
+java_genrule {
+ name: "core-icu4j-for-host.ravenwood",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":core-icu4j-for-host.ravenwood-base{ravenwood.jar}",
+ ],
+ out: [
+ "core-icu4j-for-host.ravenwood.jar",
+ ],
+}
diff --git a/android_icu4j/api/public/current.txt b/android_icu4j/api/public/current.txt
index ca5a60c..6125d38 100644
--- a/android_icu4j/api/public/current.txt
+++ b/android_icu4j/api/public/current.txt
@@ -4034,7 +4034,7 @@
method public String toPattern(boolean);
field public static final int ADD_CASE_MAPPINGS = 4; // 0x4
field public static final android.icu.text.UnicodeSet ALL_CODE_POINTS;
- field public static final int CASE = 2; // 0x2
+ field @Deprecated public static final int CASE = 2; // 0x2
field public static final int CASE_INSENSITIVE = 2; // 0x2
field public static final android.icu.text.UnicodeSet EMPTY;
field public static final int IGNORE_SPACE = 1; // 0x1
@@ -4236,7 +4236,6 @@
field public static final int AM_PM = 9; // 0x9
field public static final int APRIL = 3; // 0x3
field public static final int AUGUST = 7; // 0x7
- field @Deprecated protected static final int BASE_FIELD_COUNT = 23; // 0x17
field public static final int DATE = 5; // 0x5
field public static final int DAY_OF_MONTH = 5; // 0x5
field public static final int DAY_OF_WEEK = 7; // 0x7
diff --git a/android_icu4j/api/public/removed.txt b/android_icu4j/api/public/removed.txt
index e73b663..1da85f6 100644
--- a/android_icu4j/api/public/removed.txt
+++ b/android_icu4j/api/public/removed.txt
@@ -1,6 +1,10 @@
// Signature format: 2.0
package android.icu.util {
+ public abstract class Calendar implements java.lang.Cloneable java.lang.Comparable<android.icu.util.Calendar> java.io.Serializable {
+ field @Deprecated protected static final int BASE_FIELD_COUNT;
+ }
+
public class JapaneseCalendar extends android.icu.util.GregorianCalendar {
field @Deprecated public static final int CURRENT_ERA;
}
diff --git a/android_icu4j/icu-ravenwood-policies.txt b/android_icu4j/icu-ravenwood-policies.txt
new file mode 100644
index 0000000..208b856
--- /dev/null
+++ b/android_icu4j/icu-ravenwood-policies.txt
@@ -0,0 +1,614 @@
+# Ravenwood policy file to expose APIs under android.icu, (which is under src/main/java)
+# We do not expose APIs under com.android, which is under libcore_bridge.
+
+# This file is generated with the following:
+# $ jar tvf $ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-runtime/core-icu4j-for-host.ravenwood.jar | sed -ne 's!^.* !! ; \!\$!d ; y!/!.!; s!\.class$!!p' | grep '^android\.icu' | sed -e 's!^!class ! ; s!$! keepclass!'
+
+# On goog/master, or once AOSP gets ravenwood-stats-collector.sh, we can use the following command
+# instead.
+# $ $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/ravenwood-stats-collector.sh
+# $ sed -ne '\!\$!d ; s/ keep$/ keepclass/ ; /^class android\.icu/p' /tmp/ravenwood/ravenwood-keep-all/hoststubgen_core-icu4j-for-host_keep_all.txt
+
+# TODO(b/353573764): Switch to `package` once it's supported on aosp/main.
+
+class android.icu.impl.Assert keepclass
+class android.icu.impl.BMPSet keepclass
+class android.icu.impl.CSCharacterIterator keepclass
+class android.icu.impl.CacheBase keepclass
+class android.icu.impl.CacheValue keepclass
+class android.icu.impl.CalType keepclass
+class android.icu.impl.CalendarAstronomer keepclass
+class android.icu.impl.CalendarCache keepclass
+class android.icu.impl.CalendarUtil keepclass
+class android.icu.impl.CaseMapImpl keepclass
+class android.icu.impl.CharTrie keepclass
+class android.icu.impl.CharacterIteration keepclass
+class android.icu.impl.CharacterIteratorWrapper keepclass
+class android.icu.impl.CharacterPropertiesImpl keepclass
+class android.icu.impl.ClassLoaderUtil keepclass
+class android.icu.impl.CollectionSet keepclass
+class android.icu.impl.CurrencyData keepclass
+class android.icu.impl.DateNumberFormat keepclass
+class android.icu.impl.DayPeriodRules keepclass
+class android.icu.impl.DontCareFieldPosition keepclass
+class android.icu.impl.EmojiProps keepclass
+class android.icu.impl.EraRules keepclass
+class android.icu.impl.FormattedStringBuilder keepclass
+class android.icu.impl.FormattedValueFieldPositionIteratorImpl keepclass
+class android.icu.impl.FormattedValueStringBuilderImpl keepclass
+class android.icu.impl.Grego keepclass
+class android.icu.impl.ICUBinary keepclass
+class android.icu.impl.ICUCache keepclass
+class android.icu.impl.ICUConfig keepclass
+class android.icu.impl.ICUCurrencyDisplayInfoProvider keepclass
+class android.icu.impl.ICUCurrencyMetaInfo keepclass
+class android.icu.impl.ICUData keepclass
+class android.icu.impl.ICUDataVersion keepclass
+class android.icu.impl.ICUDebug keepclass
+class android.icu.impl.ICULangDataTables keepclass
+class android.icu.impl.ICULocaleService keepclass
+class android.icu.impl.ICUNotifier keepclass
+class android.icu.impl.ICURWLock keepclass
+class android.icu.impl.ICURegionDataTables keepclass
+class android.icu.impl.ICUResourceBundle keepclass
+class android.icu.impl.ICUResourceBundleImpl keepclass
+class android.icu.impl.ICUResourceBundleReader keepclass
+class android.icu.impl.ICUResourceTableAccess keepclass
+class android.icu.impl.ICUService keepclass
+class android.icu.impl.IDNA2003 keepclass
+class android.icu.impl.IllegalIcuArgumentException keepclass
+class android.icu.impl.IntTrie keepclass
+class android.icu.impl.IntTrieBuilder keepclass
+class android.icu.impl.InvalidFormatException keepclass
+class android.icu.impl.IterableComparator keepclass
+class android.icu.impl.JavaTimeZone keepclass
+class android.icu.impl.LocaleDisplayNamesImpl keepclass
+class android.icu.impl.LocaleFallbackData keepclass
+class android.icu.impl.LocaleIDParser keepclass
+class android.icu.impl.LocaleIDs keepclass
+class android.icu.impl.LocaleUtility keepclass
+class android.icu.impl.Norm2AllModes keepclass
+class android.icu.impl.Normalizer2Impl keepclass
+class android.icu.impl.OlsonTimeZone keepclass
+class android.icu.impl.PVecToTrieCompactHandler keepclass
+class android.icu.impl.Pair keepclass
+class android.icu.impl.PatternProps keepclass
+class android.icu.impl.PatternTokenizer keepclass
+class android.icu.impl.PluralRulesLoader keepclass
+class android.icu.impl.PropsVectors keepclass
+class android.icu.impl.Punycode keepclass
+class android.icu.impl.RBBIDataWrapper keepclass
+class android.icu.impl.Relation keepclass
+class android.icu.impl.RelativeDateFormat keepclass
+class android.icu.impl.ReplaceableUCharacterIterator keepclass
+class android.icu.impl.ResourceBundleWrapper keepclass
+class android.icu.impl.Row keepclass
+class android.icu.impl.RuleCharacterIterator keepclass
+class android.icu.impl.SimpleCache keepclass
+class android.icu.impl.SimpleFilteredSentenceBreakIterator keepclass
+class android.icu.impl.SimpleFormatterImpl keepclass
+class android.icu.impl.SoftCache keepclass
+class android.icu.impl.SortedSetRelation keepclass
+class android.icu.impl.StandardPlural keepclass
+class android.icu.impl.StaticUnicodeSets keepclass
+class android.icu.impl.StringPrepDataReader keepclass
+class android.icu.impl.StringRange keepclass
+class android.icu.impl.StringSegment keepclass
+class android.icu.impl.TZDBTimeZoneNames keepclass
+class android.icu.impl.TextTrieMap keepclass
+class android.icu.impl.TimeZoneAdapter keepclass
+class android.icu.impl.TimeZoneGenericNames keepclass
+class android.icu.impl.TimeZoneNamesFactoryImpl keepclass
+class android.icu.impl.TimeZoneNamesImpl keepclass
+class android.icu.impl.Trie keepclass
+class android.icu.impl.Trie2 keepclass
+class android.icu.impl.Trie2Writable keepclass
+class android.icu.impl.Trie2_16 keepclass
+class android.icu.impl.Trie2_32 keepclass
+class android.icu.impl.TrieBuilder keepclass
+class android.icu.impl.TrieIterator keepclass
+class android.icu.impl.UBiDiProps keepclass
+class android.icu.impl.UCaseProps keepclass
+class android.icu.impl.UCharArrayIterator keepclass
+class android.icu.impl.UCharacterIteratorWrapper keepclass
+class android.icu.impl.UCharacterName keepclass
+class android.icu.impl.UCharacterNameChoice keepclass
+class android.icu.impl.UCharacterNameReader keepclass
+class android.icu.impl.UCharacterProperty keepclass
+class android.icu.impl.UCharacterUtility keepclass
+class android.icu.impl.UPropertyAliases keepclass
+class android.icu.impl.URLHandler keepclass
+class android.icu.impl.UResource keepclass
+class android.icu.impl.USerializedSet keepclass
+class android.icu.impl.UTS46 keepclass
+class android.icu.impl.UnicodeRegex keepclass
+class android.icu.impl.UnicodeSetStringSpan keepclass
+class android.icu.impl.Utility keepclass
+class android.icu.impl.UtilityExtensions keepclass
+class android.icu.impl.ValidIdentifiers keepclass
+class android.icu.impl.ZoneMeta keepclass
+class android.icu.impl.breakiter.BurmeseBreakEngine keepclass
+class android.icu.impl.breakiter.BytesDictionaryMatcher keepclass
+class android.icu.impl.breakiter.CharsDictionaryMatcher keepclass
+class android.icu.impl.breakiter.CjkBreakEngine keepclass
+class android.icu.impl.breakiter.DictionaryBreakEngine keepclass
+class android.icu.impl.breakiter.DictionaryData keepclass
+class android.icu.impl.breakiter.DictionaryMatcher keepclass
+class android.icu.impl.breakiter.KhmerBreakEngine keepclass
+class android.icu.impl.breakiter.LSTMBreakEngine keepclass
+class android.icu.impl.breakiter.LanguageBreakEngine keepclass
+class android.icu.impl.breakiter.LaoBreakEngine keepclass
+class android.icu.impl.breakiter.MlBreakEngine keepclass
+class android.icu.impl.breakiter.ModelIndex keepclass
+class android.icu.impl.breakiter.ThaiBreakEngine keepclass
+class android.icu.impl.breakiter.UnhandledBreakEngine keepclass
+class android.icu.impl.coll.BOCSU keepclass
+class android.icu.impl.coll.Collation keepclass
+class android.icu.impl.coll.CollationBuilder keepclass
+class android.icu.impl.coll.CollationCompare keepclass
+class android.icu.impl.coll.CollationData keepclass
+class android.icu.impl.coll.CollationDataBuilder keepclass
+class android.icu.impl.coll.CollationDataReader keepclass
+class android.icu.impl.coll.CollationFCD keepclass
+class android.icu.impl.coll.CollationFastLatin keepclass
+class android.icu.impl.coll.CollationFastLatinBuilder keepclass
+class android.icu.impl.coll.CollationIterator keepclass
+class android.icu.impl.coll.CollationKeys keepclass
+class android.icu.impl.coll.CollationLoader keepclass
+class android.icu.impl.coll.CollationRoot keepclass
+class android.icu.impl.coll.CollationRootElements keepclass
+class android.icu.impl.coll.CollationRuleParser keepclass
+class android.icu.impl.coll.CollationSettings keepclass
+class android.icu.impl.coll.CollationTailoring keepclass
+class android.icu.impl.coll.CollationWeights keepclass
+class android.icu.impl.coll.ContractionsAndExpansions keepclass
+class android.icu.impl.coll.FCDIterCollationIterator keepclass
+class android.icu.impl.coll.FCDUTF16CollationIterator keepclass
+class android.icu.impl.coll.IterCollationIterator keepclass
+class android.icu.impl.coll.SharedObject keepclass
+class android.icu.impl.coll.TailoredSet keepclass
+class android.icu.impl.coll.UTF16CollationIterator keepclass
+class android.icu.impl.coll.UVector32 keepclass
+class android.icu.impl.coll.UVector64 keepclass
+class android.icu.impl.data.HolidayBundle keepclass
+class android.icu.impl.data.HolidayBundle_da keepclass
+class android.icu.impl.data.HolidayBundle_da_DK keepclass
+class android.icu.impl.data.HolidayBundle_de keepclass
+class android.icu.impl.data.HolidayBundle_de_AT keepclass
+class android.icu.impl.data.HolidayBundle_de_DE keepclass
+class android.icu.impl.data.HolidayBundle_el keepclass
+class android.icu.impl.data.HolidayBundle_el_GR keepclass
+class android.icu.impl.data.HolidayBundle_en keepclass
+class android.icu.impl.data.HolidayBundle_en_CA keepclass
+class android.icu.impl.data.HolidayBundle_en_GB keepclass
+class android.icu.impl.data.HolidayBundle_en_US keepclass
+class android.icu.impl.data.HolidayBundle_es keepclass
+class android.icu.impl.data.HolidayBundle_es_MX keepclass
+class android.icu.impl.data.HolidayBundle_fr keepclass
+class android.icu.impl.data.HolidayBundle_fr_CA keepclass
+class android.icu.impl.data.HolidayBundle_fr_FR keepclass
+class android.icu.impl.data.HolidayBundle_it keepclass
+class android.icu.impl.data.HolidayBundle_it_IT keepclass
+class android.icu.impl.data.HolidayBundle_iw keepclass
+class android.icu.impl.data.HolidayBundle_iw_IL keepclass
+class android.icu.impl.data.HolidayBundle_ja_JP keepclass
+class android.icu.impl.data.ResourceReader keepclass
+class android.icu.impl.data.TokenIterator keepclass
+class android.icu.impl.duration.BasicDurationFormat keepclass
+class android.icu.impl.duration.BasicDurationFormatter keepclass
+class android.icu.impl.duration.BasicDurationFormatterFactory keepclass
+class android.icu.impl.duration.BasicPeriodBuilderFactory keepclass
+class android.icu.impl.duration.BasicPeriodFormatter keepclass
+class android.icu.impl.duration.BasicPeriodFormatterFactory keepclass
+class android.icu.impl.duration.BasicPeriodFormatterService keepclass
+class android.icu.impl.duration.DateFormatter keepclass
+class android.icu.impl.duration.DurationFormatter keepclass
+class android.icu.impl.duration.DurationFormatterFactory keepclass
+class android.icu.impl.duration.FixedUnitBuilder keepclass
+class android.icu.impl.duration.MultiUnitBuilder keepclass
+class android.icu.impl.duration.OneOrTwoUnitBuilder keepclass
+class android.icu.impl.duration.Period keepclass
+class android.icu.impl.duration.PeriodBuilder keepclass
+class android.icu.impl.duration.PeriodBuilderFactory keepclass
+class android.icu.impl.duration.PeriodBuilderImpl keepclass
+class android.icu.impl.duration.PeriodFormatter keepclass
+class android.icu.impl.duration.PeriodFormatterFactory keepclass
+class android.icu.impl.duration.PeriodFormatterService keepclass
+class android.icu.impl.duration.SingleUnitBuilder keepclass
+class android.icu.impl.duration.TimeUnit keepclass
+class android.icu.impl.duration.TimeUnitConstants keepclass
+class android.icu.impl.duration.impl.DataRecord keepclass
+class android.icu.impl.duration.impl.PeriodFormatterData keepclass
+class android.icu.impl.duration.impl.PeriodFormatterDataService keepclass
+class android.icu.impl.duration.impl.RecordReader keepclass
+class android.icu.impl.duration.impl.RecordWriter keepclass
+class android.icu.impl.duration.impl.ResourceBasedPeriodFormatterDataService keepclass
+class android.icu.impl.duration.impl.Utils keepclass
+class android.icu.impl.duration.impl.XMLRecordReader keepclass
+class android.icu.impl.duration.impl.XMLRecordWriter keepclass
+class android.icu.impl.locale.AsciiUtil keepclass
+class android.icu.impl.locale.BaseLocale keepclass
+class android.icu.impl.locale.Extension keepclass
+class android.icu.impl.locale.InternalLocaleBuilder keepclass
+class android.icu.impl.locale.KeyTypeData keepclass
+class android.icu.impl.locale.LSR keepclass
+class android.icu.impl.locale.LanguageTag keepclass
+class android.icu.impl.locale.LikelySubtags keepclass
+class android.icu.impl.locale.LocaleDistance keepclass
+class android.icu.impl.locale.LocaleExtensions keepclass
+class android.icu.impl.locale.LocaleObjectCache keepclass
+class android.icu.impl.locale.LocaleSyntaxException keepclass
+class android.icu.impl.locale.LocaleValidityChecker keepclass
+class android.icu.impl.locale.ParseStatus keepclass
+class android.icu.impl.locale.StringTokenIterator keepclass
+class android.icu.impl.locale.UnicodeLocaleExtension keepclass
+class android.icu.impl.locale.XCldrStub keepclass
+class android.icu.impl.number.AdoptingModifierStore keepclass
+class android.icu.impl.number.AffixPatternProvider keepclass
+class android.icu.impl.number.AffixUtils keepclass
+class android.icu.impl.number.CompactData keepclass
+class android.icu.impl.number.ConstantAffixModifier keepclass
+class android.icu.impl.number.ConstantMultiFieldModifier keepclass
+class android.icu.impl.number.CurrencyPluralInfoAffixProvider keepclass
+class android.icu.impl.number.CurrencySpacingEnabledModifier keepclass
+class android.icu.impl.number.CustomSymbolCurrency keepclass
+class android.icu.impl.number.DecimalFormatProperties keepclass
+class android.icu.impl.number.DecimalQuantity keepclass
+class android.icu.impl.number.DecimalQuantity_AbstractBCD keepclass
+class android.icu.impl.number.DecimalQuantity_DualStorageBCD keepclass
+class android.icu.impl.number.Grouper keepclass
+class android.icu.impl.number.LocalizedNumberFormatterAsFormat keepclass
+class android.icu.impl.number.LongNameHandler keepclass
+class android.icu.impl.number.LongNameMultiplexer keepclass
+class android.icu.impl.number.MacroProps keepclass
+class android.icu.impl.number.MicroProps keepclass
+class android.icu.impl.number.MicroPropsGenerator keepclass
+class android.icu.impl.number.MicroPropsMutator keepclass
+class android.icu.impl.number.MixedUnitLongNameHandler keepclass
+class android.icu.impl.number.Modifier keepclass
+class android.icu.impl.number.ModifierStore keepclass
+class android.icu.impl.number.MultiplierFormatHandler keepclass
+class android.icu.impl.number.MultiplierProducer keepclass
+class android.icu.impl.number.MutablePatternModifier keepclass
+class android.icu.impl.number.Padder keepclass
+class android.icu.impl.number.PatternStringParser keepclass
+class android.icu.impl.number.PatternStringUtils keepclass
+class android.icu.impl.number.Properties keepclass
+class android.icu.impl.number.PropertiesAffixPatternProvider keepclass
+class android.icu.impl.number.RoundingUtils keepclass
+class android.icu.impl.number.SimpleModifier keepclass
+class android.icu.impl.number.UnitConversionHandler keepclass
+class android.icu.impl.number.UsagePrefsHandler keepclass
+class android.icu.impl.number.parse.AffixMatcher keepclass
+class android.icu.impl.number.parse.AffixPatternMatcher keepclass
+class android.icu.impl.number.parse.AffixTokenMatcherFactory keepclass
+class android.icu.impl.number.parse.CodePointMatcher keepclass
+class android.icu.impl.number.parse.CombinedCurrencyMatcher keepclass
+class android.icu.impl.number.parse.DecimalMatcher keepclass
+class android.icu.impl.number.parse.IgnorablesMatcher keepclass
+class android.icu.impl.number.parse.InfinityMatcher keepclass
+class android.icu.impl.number.parse.MinusSignMatcher keepclass
+class android.icu.impl.number.parse.MultiplierParseHandler keepclass
+class android.icu.impl.number.parse.NanMatcher keepclass
+class android.icu.impl.number.parse.NumberParseMatcher keepclass
+class android.icu.impl.number.parse.NumberParserImpl keepclass
+class android.icu.impl.number.parse.PaddingMatcher keepclass
+class android.icu.impl.number.parse.ParsedNumber keepclass
+class android.icu.impl.number.parse.ParsingUtils keepclass
+class android.icu.impl.number.parse.PercentMatcher keepclass
+class android.icu.impl.number.parse.PermilleMatcher keepclass
+class android.icu.impl.number.parse.PlusSignMatcher keepclass
+class android.icu.impl.number.parse.RequireAffixValidator keepclass
+class android.icu.impl.number.parse.RequireCurrencyValidator keepclass
+class android.icu.impl.number.parse.RequireDecimalSeparatorValidator keepclass
+class android.icu.impl.number.parse.RequireNumberValidator keepclass
+class android.icu.impl.number.parse.ScientificMatcher keepclass
+class android.icu.impl.number.parse.SeriesMatcher keepclass
+class android.icu.impl.number.parse.SymbolMatcher keepclass
+class android.icu.impl.number.parse.ValidationMatcher keepclass
+class android.icu.impl.number.range.PrefixInfixSuffixLengthHelper keepclass
+class android.icu.impl.number.range.RangeMacroProps keepclass
+class android.icu.impl.number.range.StandardPluralRanges keepclass
+class android.icu.impl.personname.FieldModifierImpl keepclass
+class android.icu.impl.personname.PersonNameFormatterImpl keepclass
+class android.icu.impl.personname.PersonNamePattern keepclass
+class android.icu.impl.text.RbnfScannerProviderImpl keepclass
+class android.icu.impl.units.ComplexUnitsConverter keepclass
+class android.icu.impl.units.ConversionRates keepclass
+class android.icu.impl.units.MeasureUnitImpl keepclass
+class android.icu.impl.units.SingleUnitImpl keepclass
+class android.icu.impl.units.UnitPreferences keepclass
+class android.icu.impl.units.UnitsConverter keepclass
+class android.icu.impl.units.UnitsData keepclass
+class android.icu.impl.units.UnitsRouter keepclass
+class android.icu.lang.CharSequences keepclass
+class android.icu.lang.CharacterProperties keepclass
+class android.icu.lang.UCharacter keepclass
+class android.icu.lang.UCharacterCategory keepclass
+class android.icu.lang.UCharacterDirection keepclass
+class android.icu.lang.UCharacterEnums keepclass
+class android.icu.lang.UCharacterNameIterator keepclass
+class android.icu.lang.UProperty keepclass
+class android.icu.lang.UScript keepclass
+class android.icu.lang.UScriptRun keepclass
+class android.icu.math.BigDecimal keepclass
+class android.icu.math.MathContext keepclass
+class android.icu.message2.DateTimeFormatterFactory keepclass
+class android.icu.message2.FormattedMessage keepclass
+class android.icu.message2.FormattedPlaceholder keepclass
+class android.icu.message2.Formatter keepclass
+class android.icu.message2.FormatterFactory keepclass
+class android.icu.message2.IdentityFormatterFactory keepclass
+class android.icu.message2.InputSource keepclass
+class android.icu.message2.MFDataModel keepclass
+class android.icu.message2.MFDataModelFormatter keepclass
+class android.icu.message2.MFDataModelValidator keepclass
+class android.icu.message2.MFFunctionRegistry keepclass
+class android.icu.message2.MFParseException keepclass
+class android.icu.message2.MFParser keepclass
+class android.icu.message2.MFSerializer keepclass
+class android.icu.message2.MessageFormatter keepclass
+class android.icu.message2.NumberFormatterFactory keepclass
+class android.icu.message2.OptUtils keepclass
+class android.icu.message2.PlainStringFormattedValue keepclass
+class android.icu.message2.Selector keepclass
+class android.icu.message2.SelectorFactory keepclass
+class android.icu.message2.StringUtils keepclass
+class android.icu.message2.StringView keepclass
+class android.icu.message2.TextSelectorFactory keepclass
+class android.icu.number.CompactNotation keepclass
+class android.icu.number.CurrencyPrecision keepclass
+class android.icu.number.FormattedNumber keepclass
+class android.icu.number.FormattedNumberRange keepclass
+class android.icu.number.FractionPrecision keepclass
+class android.icu.number.IntegerWidth keepclass
+class android.icu.number.LocalizedNumberFormatter keepclass
+class android.icu.number.LocalizedNumberRangeFormatter keepclass
+class android.icu.number.Notation keepclass
+class android.icu.number.NumberFormatter keepclass
+class android.icu.number.NumberFormatterImpl keepclass
+class android.icu.number.NumberFormatterSettings keepclass
+class android.icu.number.NumberPropertyMapper keepclass
+class android.icu.number.NumberRangeFormatter keepclass
+class android.icu.number.NumberRangeFormatterImpl keepclass
+class android.icu.number.NumberRangeFormatterSettings keepclass
+class android.icu.number.NumberSkeletonImpl keepclass
+class android.icu.number.Precision keepclass
+class android.icu.number.Scale keepclass
+class android.icu.number.ScientificNotation keepclass
+class android.icu.number.SimpleNotation keepclass
+class android.icu.number.SkeletonSyntaxException keepclass
+class android.icu.number.UnlocalizedNumberFormatter keepclass
+class android.icu.number.UnlocalizedNumberRangeFormatter keepclass
+class android.icu.platform.AndroidDataFiles keepclass
+class android.icu.text.AbsoluteValueSubstitution keepclass
+class android.icu.text.AlphabeticIndex keepclass
+class android.icu.text.AnyTransliterator keepclass
+class android.icu.text.ArabicShaping keepclass
+class android.icu.text.ArabicShapingException keepclass
+class android.icu.text.Bidi keepclass
+class android.icu.text.BidiClassifier keepclass
+class android.icu.text.BidiLine keepclass
+class android.icu.text.BidiRun keepclass
+class android.icu.text.BidiTransform keepclass
+class android.icu.text.BidiWriter keepclass
+class android.icu.text.BreakIterator keepclass
+class android.icu.text.BreakIteratorFactory keepclass
+class android.icu.text.BreakTransliterator keepclass
+class android.icu.text.CanonicalIterator keepclass
+class android.icu.text.CaseFoldTransliterator keepclass
+class android.icu.text.CaseMap keepclass
+class android.icu.text.CharsetDetector keepclass
+class android.icu.text.CharsetMatch keepclass
+class android.icu.text.CharsetRecog_2022 keepclass
+class android.icu.text.CharsetRecog_UTF8 keepclass
+class android.icu.text.CharsetRecog_Unicode keepclass
+class android.icu.text.CharsetRecog_mbcs keepclass
+class android.icu.text.CharsetRecog_sbcs keepclass
+class android.icu.text.CharsetRecognizer keepclass
+class android.icu.text.ChineseDateFormat keepclass
+class android.icu.text.ChineseDateFormatSymbols keepclass
+class android.icu.text.CollationElementIterator keepclass
+class android.icu.text.CollationKey keepclass
+class android.icu.text.Collator keepclass
+class android.icu.text.CollatorServiceShim keepclass
+class android.icu.text.CompactDecimalFormat keepclass
+class android.icu.text.ComposedCharIter keepclass
+class android.icu.text.CompoundTransliterator keepclass
+class android.icu.text.ConstrainedFieldPosition keepclass
+class android.icu.text.CurrencyDisplayNames keepclass
+class android.icu.text.CurrencyFormat keepclass
+class android.icu.text.CurrencyMetaInfo keepclass
+class android.icu.text.CurrencyPluralInfo keepclass
+class android.icu.text.DateFormat keepclass
+class android.icu.text.DateFormatSymbols keepclass
+class android.icu.text.DateIntervalFormat keepclass
+class android.icu.text.DateIntervalInfo keepclass
+class android.icu.text.DateTimePatternGenerator keepclass
+class android.icu.text.DecimalFormat keepclass
+class android.icu.text.DecimalFormatSymbols keepclass
+class android.icu.text.DisplayContext keepclass
+class android.icu.text.DisplayOptions keepclass
+class android.icu.text.DurationFormat keepclass
+class android.icu.text.Edits keepclass
+class android.icu.text.EscapeTransliterator keepclass
+class android.icu.text.FilteredBreakIteratorBuilder keepclass
+class android.icu.text.FilteredNormalizer2 keepclass
+class android.icu.text.FormattedValue keepclass
+class android.icu.text.FractionalPartSubstitution keepclass
+class android.icu.text.FunctionReplacer keepclass
+class android.icu.text.IDNA keepclass
+class android.icu.text.IntegralPartSubstitution keepclass
+class android.icu.text.ListFormatter keepclass
+class android.icu.text.LocaleDisplayNames keepclass
+class android.icu.text.LowercaseTransliterator keepclass
+class android.icu.text.MeasureFormat keepclass
+class android.icu.text.MessageFormat keepclass
+class android.icu.text.MessagePattern keepclass
+class android.icu.text.MessagePatternUtil keepclass
+class android.icu.text.ModulusSubstitution keepclass
+class android.icu.text.MultiplierSubstitution keepclass
+class android.icu.text.NFRule keepclass
+class android.icu.text.NFRuleSet keepclass
+class android.icu.text.NFSubstitution keepclass
+class android.icu.text.NameUnicodeTransliterator keepclass
+class android.icu.text.NormalizationTransliterator keepclass
+class android.icu.text.Normalizer keepclass
+class android.icu.text.Normalizer2 keepclass
+class android.icu.text.NullTransliterator keepclass
+class android.icu.text.NumberFormat keepclass
+class android.icu.text.NumberFormatServiceShim keepclass
+class android.icu.text.NumberingSystem keepclass
+class android.icu.text.NumeratorSubstitution keepclass
+class android.icu.text.PersonName keepclass
+class android.icu.text.PersonNameFormatter keepclass
+class android.icu.text.PluralFormat keepclass
+class android.icu.text.PluralRules keepclass
+class android.icu.text.PluralRulesSerialProxy keepclass
+class android.icu.text.Quantifier keepclass
+class android.icu.text.QuantityFormatter keepclass
+class android.icu.text.RBBINode keepclass
+class android.icu.text.RBBIRuleBuilder keepclass
+class android.icu.text.RBBIRuleParseTable keepclass
+class android.icu.text.RBBIRuleScanner keepclass
+class android.icu.text.RBBISetBuilder keepclass
+class android.icu.text.RBBISymbolTable keepclass
+class android.icu.text.RBBITableBuilder keepclass
+class android.icu.text.RBNFChinesePostProcessor keepclass
+class android.icu.text.RBNFPostProcessor keepclass
+class android.icu.text.RawCollationKey keepclass
+class android.icu.text.RbnfLenientScanner keepclass
+class android.icu.text.RbnfLenientScannerProvider keepclass
+class android.icu.text.RelativeDateTimeFormatter keepclass
+class android.icu.text.RemoveTransliterator keepclass
+class android.icu.text.Replaceable keepclass
+class android.icu.text.ReplaceableContextIterator keepclass
+class android.icu.text.ReplaceableString keepclass
+class android.icu.text.RuleBasedBreakIterator keepclass
+class android.icu.text.RuleBasedCollator keepclass
+class android.icu.text.RuleBasedNumberFormat keepclass
+class android.icu.text.RuleBasedTransliterator keepclass
+class android.icu.text.SCSU keepclass
+class android.icu.text.SameValueSubstitution keepclass
+class android.icu.text.ScientificNumberFormatter keepclass
+class android.icu.text.SearchIterator keepclass
+class android.icu.text.SelectFormat keepclass
+class android.icu.text.SimpleDateFormat keepclass
+class android.icu.text.SimpleFormatter keepclass
+class android.icu.text.SimplePersonName keepclass
+class android.icu.text.SourceTargetUtility keepclass
+class android.icu.text.SpoofChecker keepclass
+class android.icu.text.StringCharacterIterator keepclass
+class android.icu.text.StringMatcher keepclass
+class android.icu.text.StringPrep keepclass
+class android.icu.text.StringPrepParseException keepclass
+class android.icu.text.StringReplacer keepclass
+class android.icu.text.StringSearch keepclass
+class android.icu.text.StringTransform keepclass
+class android.icu.text.SymbolTable keepclass
+class android.icu.text.TimeUnitFormat keepclass
+class android.icu.text.TimeZoneFormat keepclass
+class android.icu.text.TimeZoneNames keepclass
+class android.icu.text.TitlecaseTransliterator keepclass
+class android.icu.text.Transform keepclass
+class android.icu.text.TransliterationRule keepclass
+class android.icu.text.TransliterationRuleSet keepclass
+class android.icu.text.Transliterator keepclass
+class android.icu.text.TransliteratorIDParser keepclass
+class android.icu.text.TransliteratorParser keepclass
+class android.icu.text.TransliteratorRegistry keepclass
+class android.icu.text.UCharacterIterator keepclass
+class android.icu.text.UFieldPosition keepclass
+class android.icu.text.UFormat keepclass
+class android.icu.text.UForwardCharacterIterator keepclass
+class android.icu.text.UTF16 keepclass
+class android.icu.text.UnescapeTransliterator keepclass
+class android.icu.text.UnicodeCompressor keepclass
+class android.icu.text.UnicodeDecompressor keepclass
+class android.icu.text.UnicodeFilter keepclass
+class android.icu.text.UnicodeMatcher keepclass
+class android.icu.text.UnicodeNameTransliterator keepclass
+class android.icu.text.UnicodeReplacer keepclass
+class android.icu.text.UnicodeSet keepclass
+class android.icu.text.UnicodeSetIterator keepclass
+class android.icu.text.UnicodeSetSpanner keepclass
+class android.icu.text.UppercaseTransliterator keepclass
+class android.icu.util.AnnualTimeZoneRule keepclass
+class android.icu.util.BasicTimeZone keepclass
+class android.icu.util.BuddhistCalendar keepclass
+class android.icu.util.ByteArrayWrapper keepclass
+class android.icu.util.BytesTrie keepclass
+class android.icu.util.BytesTrieBuilder keepclass
+class android.icu.util.CECalendar keepclass
+class android.icu.util.Calendar keepclass
+class android.icu.util.CaseInsensitiveString keepclass
+class android.icu.util.CharsTrie keepclass
+class android.icu.util.CharsTrieBuilder keepclass
+class android.icu.util.ChineseCalendar keepclass
+class android.icu.util.CodePointMap keepclass
+class android.icu.util.CodePointTrie keepclass
+class android.icu.util.CompactByteArray keepclass
+class android.icu.util.CompactCharArray keepclass
+class android.icu.util.CopticCalendar keepclass
+class android.icu.util.Currency keepclass
+class android.icu.util.CurrencyAmount keepclass
+class android.icu.util.CurrencyServiceShim keepclass
+class android.icu.util.DangiCalendar keepclass
+class android.icu.util.DateInterval keepclass
+class android.icu.util.DateRule keepclass
+class android.icu.util.DateTimeRule keepclass
+class android.icu.util.EasterHoliday keepclass
+class android.icu.util.EasterRule keepclass
+class android.icu.util.EthiopicCalendar keepclass
+class android.icu.util.Freezable keepclass
+class android.icu.util.GenderInfo keepclass
+class android.icu.util.GlobalizationPreferences keepclass
+class android.icu.util.GregorianCalendar keepclass
+class android.icu.util.HebrewCalendar keepclass
+class android.icu.util.HebrewHoliday keepclass
+class android.icu.util.Holiday keepclass
+class android.icu.util.ICUCloneNotSupportedException keepclass
+class android.icu.util.ICUException keepclass
+class android.icu.util.ICUInputTooLongException keepclass
+class android.icu.util.ICUUncheckedIOException keepclass
+class android.icu.util.IllformedLocaleException keepclass
+class android.icu.util.IndianCalendar keepclass
+class android.icu.util.InitialTimeZoneRule keepclass
+class android.icu.util.IslamicCalendar keepclass
+class android.icu.util.JapaneseCalendar keepclass
+class android.icu.util.LocaleData keepclass
+class android.icu.util.LocaleMatcher keepclass
+class android.icu.util.LocalePriorityList keepclass
+class android.icu.util.Measure keepclass
+class android.icu.util.MeasureUnit keepclass
+class android.icu.util.MutableCodePointTrie keepclass
+class android.icu.util.NoUnit keepclass
+class android.icu.util.Output keepclass
+class android.icu.util.OutputInt keepclass
+class android.icu.util.PersianCalendar keepclass
+class android.icu.util.Range keepclass
+class android.icu.util.RangeDateRule keepclass
+class android.icu.util.RangeValueIterator keepclass
+class android.icu.util.Region keepclass
+class android.icu.util.RuleBasedTimeZone keepclass
+class android.icu.util.STZInfo keepclass
+class android.icu.util.SimpleDateRule keepclass
+class android.icu.util.SimpleHoliday keepclass
+class android.icu.util.SimpleTimeZone keepclass
+class android.icu.util.StringTokenizer keepclass
+class android.icu.util.StringTrieBuilder keepclass
+class android.icu.util.TaiwanCalendar keepclass
+class android.icu.util.TimeArrayTimeZoneRule keepclass
+class android.icu.util.TimeUnit keepclass
+class android.icu.util.TimeUnitAmount keepclass
+class android.icu.util.TimeZone keepclass
+class android.icu.util.TimeZoneRule keepclass
+class android.icu.util.TimeZoneTransition keepclass
+class android.icu.util.ULocale keepclass
+class android.icu.util.UResourceBundle keepclass
+class android.icu.util.UResourceBundleIterator keepclass
+class android.icu.util.UResourceTypeMismatchException keepclass
+class android.icu.util.UniversalTimeScale keepclass
+class android.icu.util.VTimeZone keepclass
+class android.icu.util.ValueIterator keepclass
+class android.icu.util.VersionInfo keepclass
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/system/ZygoteHooks.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/system/ZygoteHooks.java
index 166389f..9be61a9 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/system/ZygoteHooks.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/system/ZygoteHooks.java
@@ -27,6 +27,8 @@
import android.icu.util.TimeZone;
import android.icu.util.ULocale;
+import com.android.icu.util.UResourceBundleNative;
+
import dalvik.annotation.compat.VersionCodes;
/**
@@ -85,6 +87,10 @@
// It's in the end of preload because preload and ICU4J initialization should succeed
// without this property. Otherwise, it indicates that the Android patch is not working.
System.setProperty(PROP_ICUBINARY_DATA_PATH, AndroidDataFiles.generateIcuDataPath());
+
+ // Cache the timezone bundles, e.g. metaZones.res, in Zygote due to app compat.
+ // http://b/339899412
+ UResourceBundleNative.cacheTimeZoneBundles();
}
/**
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
index 6f3b16e..da6ab4b 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TimeZoneDataFiles.java
@@ -39,7 +39,10 @@
* Returns time zone file paths for the specified file name in an array in the order they
* should be tried. See {@link AndroidDataFiles#generateIcuDataPath()} for ICU files instead.
* <ul>
- * <li>[0] - the location of the file from the time zone module under /apex (must exist).</li>
+ * <li>[0] - the location of the versioned file from the time zone module under /apex
+ * (must exist).</li>
+ * <li>[1] - old, unversioned location of the file from the time zone module under /apex. Will
+ * be removed once prebuilts are updated.</>
* </ul>
*/
// VisibleForTesting
@@ -48,7 +51,8 @@
}
public static String getTimeZoneModuleTzFile(String fileName) {
- return getTimeZoneModuleFile("tz/" + fileName);
+ return getTimeZoneModuleFile("tz/versioned/"
+ + TzDataSetVersion.currentFormatMajorVersion() + "/" + fileName);
}
// Remove from CorePlatformApi when all users in platform code are removed. http://b/123398797
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
index 4c07f84..e3dd275 100644
--- a/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
+++ b/android_icu4j/libcore_bridge/src/java/com/android/i18n/timezone/TzDataSetVersion.java
@@ -61,7 +61,9 @@
* version to 1 when doing so.
*/
// @VisibleForTesting : Keep this inline-able: it is used from CTS tests.
- public static final int CURRENT_FORMAT_MAJOR_VERSION = 7; // Android U
+ // LINT.IfChange
+ public static final int CURRENT_FORMAT_MAJOR_VERSION = 8; // Android V
+ // LINT.ThenChange(external/icu/android_icu4j/src/main/java/android/icu/platform/AndroidDataFiles.java)
/**
* Returns the major tz data format version supported by this device.
@@ -86,12 +88,9 @@
return CURRENT_FORMAT_MINOR_VERSION;
}
- /** The full major + minor tz data format version for this device. */
- private static final String FULL_CURRENT_FORMAT_VERSION_STRING =
- toFormatVersionString(CURRENT_FORMAT_MAJOR_VERSION, CURRENT_FORMAT_MINOR_VERSION);
-
- private static final int FORMAT_VERSION_STRING_LENGTH =
- FULL_CURRENT_FORMAT_VERSION_STRING.length();
+ /** The full major + minor tz data format version's length for this device. */
+ // @VisibleForTesting
+ public static final int FORMAT_VERSION_STRING_LENGTH = 7;
private static final Pattern FORMAT_VERSION_PATTERN = Pattern.compile("(\\d{3})\\.(\\d{3})");
/** A pattern that matches the IANA rules value of a rules update. e.g. "2016g" */
@@ -287,7 +286,8 @@
return value;
}
- private static String toFormatVersionString(int majorFormatVersion, int minorFormatVersion) {
+ // @VisibleForTesting
+ public static String toFormatVersionString(int majorFormatVersion, int minorFormatVersion) {
return to3DigitVersionString(majorFormatVersion)
+ "." + to3DigitVersionString(minorFormatVersion);
}
diff --git a/android_icu4j/libcore_bridge/src/java/com/android/icu/util/UResourceBundleNative.java b/android_icu4j/libcore_bridge/src/java/com/android/icu/util/UResourceBundleNative.java
new file mode 100644
index 0000000..eae5cf6
--- /dev/null
+++ b/android_icu4j/libcore_bridge/src/java/com/android/icu/util/UResourceBundleNative.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.icu.util;
+
+/**
+ * @hide
+ */
+public class UResourceBundleNative {
+ public static native void cacheTimeZoneBundles();
+}
diff --git a/android_icu4j/libcore_bridge/src/native/Register.cpp b/android_icu4j/libcore_bridge/src/native/Register.cpp
index ad7c9c1..25d53f3 100644
--- a/android_icu4j/libcore_bridge/src/native/Register.cpp
+++ b/android_icu4j/libcore_bridge/src/native/Register.cpp
@@ -53,6 +53,7 @@
REGISTER(register_com_android_icu_util_CaseMapperNative);
REGISTER(register_com_android_icu_util_Icu4cMetadata);
REGISTER(register_com_android_icu_util_LocaleNative);
+ REGISTER(register_com_android_icu_util_UResourceBundleNative);
REGISTER(register_com_android_icu_util_regex_PatternNative);
REGISTER(register_com_android_icu_util_regex_MatcherNative);
REGISTER(register_com_android_icu_util_charset_NativeConverter);
diff --git a/android_icu4j/libcore_bridge/src/native/com_android_icu_util_UResourceBundleNative.cpp b/android_icu4j/libcore_bridge/src/native/com_android_icu_util_UResourceBundleNative.cpp
new file mode 100644
index 0000000..c1615aa
--- /dev/null
+++ b/android_icu4j/libcore_bridge/src/native/com_android_icu_util_UResourceBundleNative.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#define LOG_TAG "UResourceBundleNative"
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/jni_macros.h>
+
+#include <log/log.h>
+#include "unicode/ures.h"
+#include "unicode/utypes.h"
+
+static inline void openDirectAndCloseRes(const char* res_name) {
+ UErrorCode status = U_ZERO_ERROR;
+ UResourceBundle *res = ures_openDirect(nullptr, res_name, &status);
+ if (U_FAILURE(status)) {
+ ALOGE("Failed to load ICU resource '%s': %s", res_name, u_errorName(status));
+ return;
+ }
+
+ ures_close(res);
+}
+
+static void UResourceBundleNative_cacheTimeZoneBundles(JNIEnv* env, jclass) {
+ openDirectAndCloseRes("zoneinfo64");
+ openDirectAndCloseRes("timezoneTypes");
+ openDirectAndCloseRes("metaZones");
+ openDirectAndCloseRes("windowsZones");
+}
+
+static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(UResourceBundleNative, cacheTimeZoneBundles, "()V"),
+};
+
+void register_com_android_icu_util_UResourceBundleNative(JNIEnv* env) {
+ jniRegisterNativeMethods(env, "com/android/icu/util/UResourceBundleNative", gMethods, NELEM(gMethods));
+}
diff --git a/android_icu4j/lint-baseline.xml b/android_icu4j/lint-baseline.xml
new file mode 100644
index 0000000..159b768
--- /dev/null
+++ b/android_icu4j/lint-baseline.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+ <issue
+ id="FlaggedApi"
+ message="Field `APPROXIMATELY_SIGN` is a flagged API and should be inside an `if (Flags.icuVApi())` check (or annotate the surrounding method `getFieldForType` with `@FlaggedApi(Flags.FLAG_ICU_V_API) to transfer requirement to caller`)"
+ errorLine1=" return NumberFormat.Field.APPROXIMATELY_SIGN;"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/icu/android_icu4j/src/main/java/android/icu/impl/number/AffixUtils.java"
+ line="282"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setDateTimeFormat()` is a flagged API and should be inside an `if (Flags.icuVApi())` check (or annotate the surrounding method `setDateTimeFromCalendar` with `@FlaggedApi(Flags.FLAG_ICU_V_API) to transfer requirement to caller`)"
+ errorLine1=" setDateTimeFormat(style, dateTimeFormat);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/icu/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java"
+ line="315"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getDateTimeFormat()` is a flagged API and should be inside an `if (Flags.icuVApi())` check (or annotate the surrounding method `getBestPattern` with `@FlaggedApi(Flags.FLAG_ICU_V_API) to transfer requirement to caller`)"
+ errorLine1=" getDateTimeFormat(style), 2, 2, timePattern, datePattern);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/icu/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java"
+ line="702"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `setDateTimeFormat()` is a flagged API and should be inside an `if (Flags.icuVApi())` check (or annotate the surrounding method `setDateTimeFormat` with `@FlaggedApi(Flags.FLAG_ICU_V_API) to transfer requirement to caller`)"
+ errorLine1=" setDateTimeFormat(style, dateTimeFormat);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/icu/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java"
+ line="1048"
+ column="13"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Method `getDateTimeFormat()` is a flagged API and should be inside an `if (Flags.icuVApi())` check (or annotate the surrounding method `getDateTimeFormat` with `@FlaggedApi(Flags.FLAG_ICU_V_API) to transfer requirement to caller`)"
+ errorLine1=" return getDateTimeFormat(DateFormat.MEDIUM);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="external/icu/android_icu4j/src/main/java/android/icu/text/DateTimePatternGenerator.java"
+ line="1063"
+ column="16"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `UNICODE_15_1` is a flagged API and should be inside an `if (Flags.icuVApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ICU_V_API) to transfer requirement to caller`)"
+ errorLine1=" UNICODE_15_1 = getInstance(15, 1, 0, 0);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="external/icu/android_icu4j/src/main/java/android/icu/util/VersionInfo.java"
+ line="528"
+ column="9"/>
+ </issue>
+
+ <issue
+ id="FlaggedApi"
+ message="Field `UNICODE_15_1` is a flagged API and should be inside an `if (Flags.icuVApi())` check (or annotate the surrounding method `?` with `@FlaggedApi(Flags.FLAG_ICU_V_API) to transfer requirement to caller`)"
+ errorLine1=" UNICODE_VERSION = UNICODE_15_1;"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="external/icu/android_icu4j/src/main/java/android/icu/util/VersionInfo.java"
+ line="532"
+ column="27"/>
+ </issue>
+
+</issues>
diff --git a/android_icu4j/src/icu74/Android.bp b/android_icu4j/src/icu74/Android.bp
new file mode 100644
index 0000000..e908532
--- /dev/null
+++ b/android_icu4j/src/icu74/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_team: "trendy_team_java_core_libraries",
+ default_visibility: ["//visibility:private"],
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_icu_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ // SPDX-license-identifier-BSD
+ // SPDX-license-identifier-ICU
+ // SPDX-license-identifier-MIT
+ // SPDX-license-identifier-Unicode-DFS
+ // legacy_unencumbered
+ default_applicable_licenses: ["external_icu_license"],
+}
+
+// Small static library used by TwilightService in the system server and WallpaperPicker2 app. To
+// avoid @CorePlaformApi, the system server doesn't use CalendarAstronomer in android.icu.
+// Don't link this in boot classpath or Zygote to avoid class collision with the
+// com.ibm.icu.impl.CalendarAstronomer in the app classloader.
+java_library_static {
+ name: "icu4j_calendar_astronomer",
+ host_supported: false,
+ sdk_version: "core_current",
+ srcs: ["main/java/com/ibm/icu/impl/CalendarAstronomer.java"],
+ visibility: [
+ "//frameworks/base/services/core",
+ "//packages/apps/WallpaperPicker2",
+ ],
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/CalendarAstronomer.java b/android_icu4j/src/icu74/main/java/com/ibm/icu/impl/CalendarAstronomer.java
similarity index 100%
rename from icu4j/main/classes/core/src/com/ibm/icu/impl/CalendarAstronomer.java
rename to android_icu4j/src/icu74/main/java/com/ibm/icu/impl/CalendarAstronomer.java
diff --git a/android_icu4j/src/main/java/android/icu/impl/CalType.java b/android_icu4j/src/main/java/android/icu/impl/CalType.java
index 52ad03e..e860bf9 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CalType.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CalType.java
@@ -28,9 +28,7 @@
ISLAMIC_UMALQURA("islamic-umalqura"),
JAPANESE("japanese"),
PERSIAN("persian"),
- ROC("roc"),
-
- UNKNOWN("unknown");
+ ROC("roc");
String id;
diff --git a/android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java b/android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java
index 088100d..e616c9c 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java
@@ -11,7 +11,6 @@
package android.icu.impl;
import java.util.Date;
-import java.util.TimeZone;
/**
* <code>CalendarAstronomer</code> is a class that can perform the calculations to
@@ -207,15 +206,6 @@
/**
* Construct a new <code>CalendarAstronomer</code> object that is initialized to
- * the specified date and time.
- * @hide draft / provisional / internal are hidden on Android
- */
- public CalendarAstronomer(Date d) {
- this(d.getTime());
- }
-
- /**
- * Construct a new <code>CalendarAstronomer</code> object that is initialized to
* the specified time. The time is expressed as a number of milliseconds since
* January 1, 1970 AD (Gregorian).
*
@@ -226,32 +216,9 @@
time = aTime;
}
- /**
- * Construct a new <code>CalendarAstronomer</code> object with the given
- * latitude and longitude. The object's time is set to the current
- * date and time.
- * <p>
- * @param longitude The desired longitude, in <em>degrees</em> east of
- * the Greenwich meridian.
- *
- * @param latitude The desired latitude, in <em>degrees</em>. Positive
- * values signify North, negative South.
- *
- * @see java.util.Date#getTime()
- * @hide draft / provisional / internal are hidden on Android
- */
- public CalendarAstronomer(double longitude, double latitude) {
- this();
- fLongitude = normPI(longitude * DEG_RAD);
- fLatitude = normPI(latitude * DEG_RAD);
- fGmtOffset = (long)(fLongitude * 24 * HOUR_MS / PI2);
- }
-
-
//-------------------------------------------------------------------------
// Time and date getters and setters
//-------------------------------------------------------------------------
-
/**
* Set the current date and time of this <code>CalendarAstronomer</code> object. All
* astronomical calculations are performed based on this time setting.
@@ -268,19 +235,6 @@
clearCache();
}
- /**
- * Set the current date and time of this <code>CalendarAstronomer</code> object. All
- * astronomical calculations are performed based on this time setting.
- *
- * @param date the time and date, expressed as a <code>Date</code> object.
- *
- * @see #setTime
- * @see #getDate
- * @hide draft / provisional / internal are hidden on Android
- */
- public void setDate(Date date) {
- setTime(date.getTime());
- }
/**
* Set the current date and time of this <code>CalendarAstronomer</code> object. All
@@ -343,77 +297,6 @@
return julianDay;
}
- /**
- * Return this object's time expressed in julian centuries:
- * the number of centuries after 1/1/1900 AD, 12:00 GMT
- *
- * @see #getJulianDay
- * @hide draft / provisional / internal are hidden on Android
- */
- public double getJulianCentury() {
- if (julianCentury == INVALID) {
- julianCentury = (getJulianDay() - 2415020.0) / 36525;
- }
- return julianCentury;
- }
-
- /**
- * Returns the current Greenwich sidereal time, measured in hours
- * @hide draft / provisional / internal are hidden on Android
- */
- public double getGreenwichSidereal() {
- if (siderealTime == INVALID) {
- // See page 86 of "Practical Astronomy with your Calculator",
- // by Peter Duffet-Smith, for details on the algorithm.
-
- double UT = normalize((double)time/HOUR_MS, 24);
-
- siderealTime = normalize(getSiderealOffset() + UT*1.002737909, 24);
- }
- return siderealTime;
- }
-
- private double getSiderealOffset() {
- if (siderealT0 == INVALID) {
- double JD = Math.floor(getJulianDay() - 0.5) + 0.5;
- double S = JD - 2451545.0;
- double T = S / 36525.0;
- siderealT0 = normalize(6.697374558 + 2400.051336*T + 0.000025862*T*T, 24);
- }
- return siderealT0;
- }
-
- /**
- * Returns the current local sidereal time, measured in hours
- * @hide draft / provisional / internal are hidden on Android
- */
- public double getLocalSidereal() {
- return normalize(getGreenwichSidereal() + (double)fGmtOffset/HOUR_MS, 24);
- }
-
- /**
- * Converts local sidereal time to Universal Time.
- *
- * @param lst The Local Sidereal Time, in hours since sidereal midnight
- * on this object's current date.
- *
- * @return The corresponding Universal Time, in milliseconds since
- * 1 Jan 1970, GMT.
- */
- private long lstToUT(double lst) {
- // Convert to local mean time
- double lt = normalize((lst - getSiderealOffset()) * 0.9972695663, 24);
-
- // Then find local midnight on this day
- long base = DAY_MS * ((time + fGmtOffset)/DAY_MS) - fGmtOffset;
-
- //out(" lt =" + lt + " hours");
- //out(" base=" + new Date(base));
-
- return base + (long)(lt * HOUR_MS);
- }
-
-
//-------------------------------------------------------------------------
// Coordinate transformations, all based on the current time of this object
//-------------------------------------------------------------------------
@@ -421,18 +304,6 @@
/**
* Convert from ecliptic to equatorial coordinates.
*
- * @param ecliptic A point in the sky in ecliptic coordinates.
- * @return The corresponding point in equatorial coordinates.
- * @hide draft / provisional / internal are hidden on Android
- */
- public final Equatorial eclipticToEquatorial(Ecliptic ecliptic)
- {
- return eclipticToEquatorial(ecliptic.longitude, ecliptic.latitude);
- }
-
- /**
- * Convert from ecliptic to equatorial coordinates.
- *
* @param eclipLong The ecliptic longitude
* @param eclipLat The ecliptic latitude
*
@@ -459,42 +330,6 @@
Math.asin(sinB*cosE + cosB*sinE*sinL) );
}
- /**
- * Convert from ecliptic longitude to equatorial coordinates.
- *
- * @param eclipLong The ecliptic longitude
- *
- * @return The corresponding point in equatorial coordinates.
- * @hide draft / provisional / internal are hidden on Android
- */
- public final Equatorial eclipticToEquatorial(double eclipLong)
- {
- return eclipticToEquatorial(eclipLong, 0); // TODO: optimize
- }
-
- /**
- * @hide draft / provisional / internal are hidden on Android
- */
- public Horizon eclipticToHorizon(double eclipLong)
- {
- Equatorial equatorial = eclipticToEquatorial(eclipLong);
-
- double H = getLocalSidereal()*PI/12 - equatorial.ascension; // Hour-angle
-
- double sinH = Math.sin(H);
- double cosH = Math.cos(H);
- double sinD = Math.sin(equatorial.declination);
- double cosD = Math.cos(equatorial.declination);
- double sinL = Math.sin(fLatitude);
- double cosL = Math.cos(fLatitude);
-
- double altitude = Math.asin(sinD*sinL + cosD*cosL*cosH);
- double azimuth = Math.atan2(-cosD*cosL*sinH, sinD - sinL * Math.sin(altitude));
-
- return new Horizon(azimuth, altitude);
- }
-
-
//-------------------------------------------------------------------------
// The Sun
//-------------------------------------------------------------------------
@@ -608,45 +443,12 @@
};
}
- /**
- * The position of the sun at this object's current date and time,
- * in equatorial coordinates.
- * @hide draft / provisional / internal are hidden on Android
- */
- public Equatorial getSunPosition() {
- return eclipticToEquatorial(getSunLongitude(), 0);
- }
-
private static class SolarLongitude {
double value;
SolarLongitude(double val) { value = val; }
}
/**
- * Constant representing the vernal equinox.
- * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}.
- * Note: In this case, "vernal" refers to the northern hemisphere's seasons.
- * @hide draft / provisional / internal are hidden on Android
- */
- public static final SolarLongitude VERNAL_EQUINOX = new SolarLongitude(0);
-
- /**
- * Constant representing the summer solstice.
- * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}.
- * Note: In this case, "summer" refers to the northern hemisphere's seasons.
- * @hide draft / provisional / internal are hidden on Android
- */
- public static final SolarLongitude SUMMER_SOLSTICE = new SolarLongitude(PI/2);
-
- /**
- * Constant representing the autumnal equinox.
- * For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}.
- * Note: In this case, "autumn" refers to the northern hemisphere's seasons.
- * @hide draft / provisional / internal are hidden on Android
- */
- public static final SolarLongitude AUTUMN_EQUINOX = new SolarLongitude(PI);
-
- /**
* Constant representing the winter solstice.
* For use with {@link #getSunTime(SolarLongitude, boolean) getSunTime}.
* Note: In this case, "winter" refers to the northern hemisphere's seasons.
@@ -678,312 +480,6 @@
return getSunTime(desired.value, next);
}
- /**
- * Returns the time (GMT) of sunrise or sunset on the local date to which
- * this calendar is currently set.
- *
- * NOTE: This method only works well if this object is set to a
- * time near local noon. Because of variations between the local
- * official time zone and the geographic longitude, the
- * computation can flop over into an adjacent day if this object
- * is set to a time near local midnight.
- *
- * @hide draft / provisional / internal are hidden on Android
- */
- public long getSunRiseSet(boolean rise) {
- long t0 = time;
-
- // Make a rough guess: 6am or 6pm local time on the current day
- long noon = ((time + fGmtOffset)/DAY_MS)*DAY_MS - fGmtOffset + 12*HOUR_MS;
-
- setTime(noon + (rise ? -6L : 6L) * HOUR_MS);
-
- long t = riseOrSet(new CoordFunc() {
- @Override
- public Equatorial eval() { return getSunPosition(); }
- },
- rise,
- .533 * DEG_RAD, // Angular Diameter
- 34 /60.0 * DEG_RAD, // Refraction correction
- MINUTE_MS / 12); // Desired accuracy
-
- setTime(t0);
- return t;
- }
-
-// Commented out - currently unused. ICU 2.6, Alan
-// //-------------------------------------------------------------------------
-// // Alternate Sun Rise/Set
-// // See Duffett-Smith p.93
-// //-------------------------------------------------------------------------
-//
-// // This yields worse results (as compared to USNO data) than getSunRiseSet().
-// /**
-// * TODO Make this public when the entire class is package-private.
-// */
-// /*public*/ long getSunRiseSet2(boolean rise) {
-// // 1. Calculate coordinates of the sun's center for midnight
-// double jd = Math.floor(getJulianDay() - 0.5) + 0.5;
-// double[] sl = getSunLongitude(jd);
-// double lambda1 = sl[0];
-// Equatorial pos1 = eclipticToEquatorial(lambda1, 0);
-//
-// // 2. Add ... to lambda to get position 24 hours later
-// double lambda2 = lambda1 + 0.985647*DEG_RAD;
-// Equatorial pos2 = eclipticToEquatorial(lambda2, 0);
-//
-// // 3. Calculate LSTs of rising and setting for these two positions
-// double tanL = Math.tan(fLatitude);
-// double H = Math.acos(-tanL * Math.tan(pos1.declination));
-// double lst1r = (PI2 + pos1.ascension - H) * 24 / PI2;
-// double lst1s = (pos1.ascension + H) * 24 / PI2;
-// H = Math.acos(-tanL * Math.tan(pos2.declination));
-// double lst2r = (PI2-H + pos2.ascension ) * 24 / PI2;
-// double lst2s = (H + pos2.ascension ) * 24 / PI2;
-// if (lst1r > 24) lst1r -= 24;
-// if (lst1s > 24) lst1s -= 24;
-// if (lst2r > 24) lst2r -= 24;
-// if (lst2s > 24) lst2s -= 24;
-//
-// // 4. Convert LSTs to GSTs. If GST1 > GST2, add 24 to GST2.
-// double gst1r = lstToGst(lst1r);
-// double gst1s = lstToGst(lst1s);
-// double gst2r = lstToGst(lst2r);
-// double gst2s = lstToGst(lst2s);
-// if (gst1r > gst2r) gst2r += 24;
-// if (gst1s > gst2s) gst2s += 24;
-//
-// // 5. Calculate GST at 0h UT of this date
-// double t00 = utToGst(0);
-//
-// // 6. Calculate GST at 0h on the observer's longitude
-// double offset = Math.round(fLongitude*12/PI); // p.95 step 6; he _rounds_ to nearest 15 deg.
-// double t00p = t00 - offset*1.002737909;
-// if (t00p < 0) t00p += 24; // do NOT normalize
-//
-// // 7. Adjust
-// if (gst1r < t00p) {
-// gst1r += 24;
-// gst2r += 24;
-// }
-// if (gst1s < t00p) {
-// gst1s += 24;
-// gst2s += 24;
-// }
-//
-// // 8.
-// double gstr = (24.07*gst1r-t00*(gst2r-gst1r))/(24.07+gst1r-gst2r);
-// double gsts = (24.07*gst1s-t00*(gst2s-gst1s))/(24.07+gst1s-gst2s);
-//
-// // 9. Correct for parallax, refraction, and sun's diameter
-// double dec = (pos1.declination + pos2.declination) / 2;
-// double psi = Math.acos(Math.sin(fLatitude) / Math.cos(dec));
-// double x = 0.830725 * DEG_RAD; // parallax+refraction+diameter
-// double y = Math.asin(Math.sin(x) / Math.sin(psi)) * RAD_DEG;
-// double delta_t = 240 * y / Math.cos(dec) / 3600; // hours
-//
-// // 10. Add correction to GSTs, subtract from GSTr
-// gstr -= delta_t;
-// gsts += delta_t;
-//
-// // 11. Convert GST to UT and then to local civil time
-// double ut = gstToUt(rise ? gstr : gsts);
-// //System.out.println((rise?"rise=":"set=") + ut + ", delta_t=" + delta_t);
-// long midnight = DAY_MS * (time / DAY_MS); // Find UT midnight on this day
-// return midnight + (long) (ut * 3600000);
-// }
-
-// Commented out - currently unused. ICU 2.6, Alan
-// /**
-// * Convert local sidereal time to Greenwich sidereal time.
-// * Section 15. Duffett-Smith p.21
-// * @param lst in hours (0..24)
-// * @return GST in hours (0..24)
-// */
-// double lstToGst(double lst) {
-// double delta = fLongitude * 24 / PI2;
-// return normalize(lst - delta, 24);
-// }
-
-// Commented out - currently unused. ICU 2.6, Alan
-// /**
-// * Convert UT to GST on this date.
-// * Section 12. Duffett-Smith p.17
-// * @param ut in hours
-// * @return GST in hours
-// */
-// double utToGst(double ut) {
-// return normalize(getT0() + ut*1.002737909, 24);
-// }
-
-// Commented out - currently unused. ICU 2.6, Alan
-// /**
-// * Convert GST to UT on this date.
-// * Section 13. Duffett-Smith p.18
-// * @param gst in hours
-// * @return UT in hours
-// */
-// double gstToUt(double gst) {
-// return normalize(gst - getT0(), 24) * 0.9972695663;
-// }
-
-// Commented out - currently unused. ICU 2.6, Alan
-// double getT0() {
-// // Common computation for UT <=> GST
-//
-// // Find JD for 0h UT
-// double jd = Math.floor(getJulianDay() - 0.5) + 0.5;
-//
-// double s = jd - 2451545.0;
-// double t = s / 36525.0;
-// double t0 = 6.697374558 + (2400.051336 + 0.000025862*t)*t;
-// return t0;
-// }
-
-// Commented out - currently unused. ICU 2.6, Alan
-// //-------------------------------------------------------------------------
-// // Alternate Sun Rise/Set
-// // See sci.astro FAQ
-// // http://www.faqs.org/faqs/astronomy/faq/part3/section-5.html
-// //-------------------------------------------------------------------------
-//
-// // Note: This method appears to produce inferior accuracy as
-// // compared to getSunRiseSet().
-//
-// /**
-// * TODO Make this public when the entire class is package-private.
-// */
-// /*public*/ long getSunRiseSet3(boolean rise) {
-//
-// // Compute day number for 0.0 Jan 2000 epoch
-// double d = (double)(time - EPOCH_2000_MS) / DAY_MS;
-//
-// // Now compute the Local Sidereal Time, LST:
-// //
-// double LST = 98.9818 + 0.985647352 * d + /*UT*15 + long*/
-// fLongitude*RAD_DEG;
-// //
-// // (east long. positive). Note that LST is here expressed in degrees,
-// // where 15 degrees corresponds to one hour. Since LST really is an angle,
-// // it's convenient to use one unit---degrees---throughout.
-//
-// // COMPUTING THE SUN'S POSITION
-// // ----------------------------
-// //
-// // To be able to compute the Sun's rise/set times, you need to be able to
-// // compute the Sun's position at any time. First compute the "day
-// // number" d as outlined above, for the desired moment. Next compute:
-// //
-// double oblecl = 23.4393 - 3.563E-7 * d;
-// //
-// double w = 282.9404 + 4.70935E-5 * d;
-// double M = 356.0470 + 0.9856002585 * d;
-// double e = 0.016709 - 1.151E-9 * d;
-// //
-// // This is the obliquity of the ecliptic, plus some of the elements of
-// // the Sun's apparent orbit (i.e., really the Earth's orbit): w =
-// // argument of perihelion, M = mean anomaly, e = eccentricity.
-// // Semi-major axis is here assumed to be exactly 1.0 (while not strictly
-// // true, this is still an accurate approximation). Next compute E, the
-// // eccentric anomaly:
-// //
-// double E = M + e*(180/PI) * Math.sin(M*DEG_RAD) * ( 1.0 + e*Math.cos(M*DEG_RAD) );
-// //
-// // where E and M are in degrees. This is it---no further iterations are
-// // needed because we know e has a sufficiently small value. Next compute
-// // the true anomaly, v, and the distance, r:
-// //
-// /* r * cos(v) = */ double A = Math.cos(E*DEG_RAD) - e;
-// /* r * sin(v) = */ double B = Math.sqrt(1 - e*e) * Math.sin(E*DEG_RAD);
-// //
-// // and
-// //
-// // r = sqrt( A*A + B*B )
-// double v = Math.atan2( B, A )*RAD_DEG;
-// //
-// // The Sun's true longitude, slon, can now be computed:
-// //
-// double slon = v + w;
-// //
-// // Since the Sun is always at the ecliptic (or at least very very close to
-// // it), we can use simplified formulae to convert slon (the Sun's ecliptic
-// // longitude) to sRA and sDec (the Sun's RA and Dec):
-// //
-// // sin(slon) * cos(oblecl)
-// // tan(sRA) = -------------------------
-// // cos(slon)
-// //
-// // sin(sDec) = sin(oblecl) * sin(slon)
-// //
-// // As was the case when computing az, the Azimuth, if possible use an
-// // atan2() function to compute sRA.
-//
-// double sRA = Math.atan2(Math.sin(slon*DEG_RAD) * Math.cos(oblecl*DEG_RAD), Math.cos(slon*DEG_RAD))*RAD_DEG;
-//
-// double sin_sDec = Math.sin(oblecl*DEG_RAD) * Math.sin(slon*DEG_RAD);
-// double sDec = Math.asin(sin_sDec)*RAD_DEG;
-//
-// // COMPUTING RISE AND SET TIMES
-// // ----------------------------
-// //
-// // To compute when an object rises or sets, you must compute when it
-// // passes the meridian and the HA of rise/set. Then the rise time is
-// // the meridian time minus HA for rise/set, and the set time is the
-// // meridian time plus the HA for rise/set.
-// //
-// // To find the meridian time, compute the Local Sidereal Time at 0h local
-// // time (or 0h UT if you prefer to work in UT) as outlined above---name
-// // that quantity LST0. The Meridian Time, MT, will now be:
-// //
-// // MT = RA - LST0
-// double MT = normalize(sRA - LST, 360);
-// //
-// // where "RA" is the object's Right Ascension (in degrees!). If negative,
-// // add 360 deg to MT. If the object is the Sun, leave the time as it is,
-// // but if it's stellar, multiply MT by 365.2422/366.2422, to convert from
-// // sidereal to solar time. Now, compute HA for rise/set, name that
-// // quantity HA0:
-// //
-// // sin(h0) - sin(lat) * sin(Dec)
-// // cos(HA0) = ---------------------------------
-// // cos(lat) * cos(Dec)
-// //
-// // where h0 is the altitude selected to represent rise/set. For a purely
-// // mathematical horizon, set h0 = 0 and simplify to:
-// //
-// // cos(HA0) = - tan(lat) * tan(Dec)
-// //
-// // If you want to account for refraction on the atmosphere, set h0 = -35/60
-// // degrees (-35 arc minutes), and if you want to compute the rise/set times
-// // for the Sun's upper limb, set h0 = -50/60 (-50 arc minutes).
-// //
-// double h0 = -50/60 * DEG_RAD;
-//
-// double HA0 = Math.acos(
-// (Math.sin(h0) - Math.sin(fLatitude) * sin_sDec) /
-// (Math.cos(fLatitude) * Math.cos(sDec*DEG_RAD)))*RAD_DEG;
-//
-// // When HA0 has been computed, leave it as it is for the Sun but multiply
-// // by 365.2422/366.2422 for stellar objects, to convert from sidereal to
-// // solar time. Finally compute:
-// //
-// // Rise time = MT - HA0
-// // Set time = MT + HA0
-// //
-// // convert the times from degrees to hours by dividing by 15.
-// //
-// // If you'd like to check that your calculations are accurate or just
-// // need a quick result, check the USNO's Sun or Moon Rise/Set Table,
-// // <URL:http://aa.usno.navy.mil/AA/data/docs/RS_OneYear.html>.
-//
-// double result = MT + (rise ? -HA0 : HA0); // in degrees
-//
-// // Find UT midnight on this day
-// long midnight = DAY_MS * (time / DAY_MS);
-//
-// return midnight + (long) (result * 3600000 / 15);
-// }
-
//-------------------------------------------------------------------------
// The Moon
//-------------------------------------------------------------------------
@@ -1050,7 +546,7 @@
double a4 = 0.2140*PI/180 * Math.sin(2 * meanAnomalyMoon);
// Now find the moon's corrected longitude
- moonLongitude = meanLongitude + evection + center - annual + a4;
+ double moonLongitude = meanLongitude + evection + center - annual + a4;
//
// And finally, find the variation, caused by the fact that the sun's
@@ -1104,26 +600,6 @@
return norm2PI(moonEclipLong - sunLongitude);
}
- /**
- * Calculate the phase of the moon at the time set in this object.
- * The returned phase is a <code>double</code> in the range
- * <code>0 <= phase < 1</code>, interpreted as follows:
- * <ul>
- * <li>0.00: New moon
- * <li>0.25: First quarter
- * <li>0.50: Full moon
- * <li>0.75: Last quarter
- * </ul>
- *
- * @see #getMoonAge
- * @hide draft / provisional / internal are hidden on Android
- */
- public double getMoonPhase() {
- // See page 147 of "Practical Astronomy with your Calculator",
- // by Peter Duffet-Smith, for details on the algorithm.
- return 0.5 * (1 - Math.cos(getMoonAge()));
- }
-
private static class MoonAge {
double value;
MoonAge(double val) { value = val; }
@@ -1137,27 +613,6 @@
public static final MoonAge NEW_MOON = new MoonAge(0);
/**
- * Constant representing the moon's first quarter.
- * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime}
- * @hide draft / provisional / internal are hidden on Android
- */
- public static final MoonAge FIRST_QUARTER = new MoonAge(PI/2);
-
- /**
- * Constant representing a full moon.
- * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime}
- * @hide draft / provisional / internal are hidden on Android
- */
- public static final MoonAge FULL_MOON = new MoonAge(PI);
-
- /**
- * Constant representing the moon's last quarter.
- * For use with {@link #getMoonTime(MoonAge, boolean) getMoonTime}
- * @hide draft / provisional / internal are hidden on Android
- */
- public static final MoonAge LAST_QUARTER = new MoonAge((PI*3)/2);
-
- /**
* Find the next or previous time at which the Moon's ecliptic
* longitude will have the desired value.
* <p>
@@ -1190,23 +645,6 @@
return getMoonTime(desired.value, next);
}
- /**
- * Returns the time (GMT) of sunrise or sunset on the local date to which
- * this calendar is currently set.
- * @hide draft / provisional / internal are hidden on Android
- */
- public long getMoonRiseSet(boolean rise)
- {
- return riseOrSet(new CoordFunc() {
- @Override
- public Equatorial eval() { return getMoonPosition(); }
- },
- rise,
- .533 * DEG_RAD, // Angular Diameter
- 34 /60.0 * DEG_RAD, // Refraction correction
- MINUTE_MS); // Desired accuracy
- }
-
//-------------------------------------------------------------------------
// Interpolation methods for finding the time at which a given event occurs
//-------------------------------------------------------------------------
@@ -1283,48 +721,6 @@
return time;
}
- private interface CoordFunc {
- public Equatorial eval();
- }
-
- private long riseOrSet(CoordFunc func, boolean rise,
- double diameter, double refraction,
- long epsilon)
- {
- Equatorial pos = null;
- double tanL = Math.tan(fLatitude);
- long deltaT = Long.MAX_VALUE;
- int count = 0;
-
- //
- // Calculate the object's position at the current time, then use that
- // position to calculate the time of rising or setting. The position
- // will be different at that time, so iterate until the error is allowable.
- //
- do {
- // See "Practical Astronomy With Your Calculator, section 33.
- pos = func.eval();
- double angle = Math.acos(-tanL * Math.tan(pos.declination));
- double lst = ((rise ? PI2-angle : angle) + pos.ascension ) * 24 / PI2;
-
- // Convert from LST to Universal Time.
- long newTime = lstToUT( lst );
-
- deltaT = newTime - time;
- setTime(newTime);
- }
- while (++ count < 5 && Math.abs(deltaT) > epsilon);
-
- // Calculate the correction due to refraction and the object's angular diameter
- double cosD = Math.cos(pos.declination);
- double psi = Math.acos(Math.sin(fLatitude) / cosD);
- double x = diameter / 2 + refraction;
- double y = Math.asin(Math.sin(x) / Math.sin(psi));
- long delta = (long)((240 * y * RAD_DEG / cosD)*SECOND_MS);
-
- return time + (rise ? -delta : delta);
- }
-
//-------------------------------------------------------------------------
// Other utility methods
//-------------------------------------------------------------------------
@@ -1391,19 +787,16 @@
* measured in radians.
*/
private double eclipticObliquity() {
- if (eclipObliquity == INVALID) {
- final double epoch = 2451545.0; // 2000 AD, January 1.5
+ final double epoch = 2451545.0; // 2000 AD, January 1.5
- double T = (getJulianDay() - epoch) / 36525;
+ double T = (getJulianDay() - epoch) / 36525;
- eclipObliquity = 23.439292
+ double eclipObliquity = 23.439292
- 46.815/3600 * T
- 0.0006/3600 * T*T
+ 0.00181/3600 * T*T*T;
- eclipObliquity *= DEG_RAD;
- }
- return eclipObliquity;
+ return eclipObliquity * DEG_RAD;
}
@@ -1417,13 +810,6 @@
*/
private long time;
- /* These aren't used yet, but they'll be needed for sunset calculations
- * and equatorial to horizon coordinate conversions
- */
- private double fLongitude = 0.0;
- private double fLatitude = 0.0;
- private long fGmtOffset = 0;
-
//
// The following fields are used to cache calculated results for improved
// performance. These values all depend on the current time setting
@@ -1432,52 +818,20 @@
static final private double INVALID = Double.MIN_VALUE;
private transient double julianDay = INVALID;
- private transient double julianCentury = INVALID;
private transient double sunLongitude = INVALID;
private transient double meanAnomalySun = INVALID;
- private transient double moonLongitude = INVALID;
private transient double moonEclipLong = INVALID;
- //private transient double meanAnomalyMoon = INVALID;
- private transient double eclipObliquity = INVALID;
- private transient double siderealT0 = INVALID;
- private transient double siderealTime = INVALID;
private transient Equatorial moonPosition = null;
private void clearCache() {
julianDay = INVALID;
- julianCentury = INVALID;
sunLongitude = INVALID;
meanAnomalySun = INVALID;
- moonLongitude = INVALID;
moonEclipLong = INVALID;
- //meanAnomalyMoon = INVALID;
- eclipObliquity = INVALID;
- siderealTime = INVALID;
- siderealT0 = INVALID;
moonPosition = null;
}
- //private static void out(String s) {
- // System.out.println(s);
- //}
-
- //private static String deg(double rad) {
- // return Double.toString(rad * RAD_DEG);
- //}
-
- //private static String hours(long ms) {
- // return Double.toString((double)ms / HOUR_MS) + " hours";
- //}
-
- /**
- * @hide draft / provisional / internal are hidden on Android
- */
- public String local(long localMillis) {
- return new Date(localMillis - TimeZone.getDefault().getRawOffset()).toString();
- }
-
-
/**
* Represents the position of an object in the sky relative to the ecliptic,
* the plane of the earth's orbit around the Sun.
@@ -1492,7 +846,6 @@
* value without worrying about whether other code will modify them.
*
* @see CalendarAstronomer.Equatorial
- * @see CalendarAstronomer.Horizon
* @hide Only a subset of ICU is exposed in Android
* @hide draft / provisional / internal are hidden on Android
*/
@@ -1553,7 +906,6 @@
* value without worrying about whether other code will modify them.
*
* @see CalendarAstronomer.Ecliptic
- * @see CalendarAstronomer.Horizon
* @hide Only a subset of ICU is exposed in Android
* @hide draft / provisional / internal are hidden on Android
*/
@@ -1607,60 +959,6 @@
public final double declination;
}
- /**
- * Represents the position of an object in the sky relative to
- * the local horizon.
- * The <i>Altitude</i> represents the object's elevation above the horizon,
- * with objects below the horizon having a negative altitude.
- * The <i>Azimuth</i> is the geographic direction of the object from the
- * observer's position, with 0 representing north. The azimuth increases
- * clockwise from north.
- * <p>
- * Note that Horizon objects are immutable and cannot be modified
- * once they are constructed. This allows them to be passed and returned by
- * value without worrying about whether other code will modify them.
- *
- * @see CalendarAstronomer.Ecliptic
- * @see CalendarAstronomer.Equatorial
- * @hide Only a subset of ICU is exposed in Android
- * @hide draft / provisional / internal are hidden on Android
- */
- public static final class Horizon {
- /**
- * Constructs a Horizon coordinate object.
- * <p>
- * @param alt The altitude, measured in radians above the horizon.
- * @param azim The azimuth, measured in radians clockwise from north.
- * @hide draft / provisional / internal are hidden on Android
- */
- public Horizon(double alt, double azim) {
- altitude = alt;
- azimuth = azim;
- }
-
- /**
- * Return a string representation of this object, with the
- * angles measured in degrees.
- * @hide draft / provisional / internal are hidden on Android
- */
- @Override
- public String toString() {
- return Double.toString(altitude*RAD_DEG) + "," + (azimuth*RAD_DEG);
- }
-
- /**
- * The object's altitude above the horizon, in radians.
- * @hide draft / provisional / internal are hidden on Android
- */
- public final double altitude;
-
- /**
- * The object's direction, in radians clockwise from north.
- * @hide draft / provisional / internal are hidden on Android
- */
- public final double azimuth;
- }
-
static private String radToHms(double angle) {
int hrs = (int) (angle*RAD_HOUR);
int min = (int)((angle*RAD_HOUR - hrs) * 60);
diff --git a/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java b/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java
index 24c5487..d87fbe1 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CaseMapImpl.java
@@ -911,7 +911,8 @@
// State bits.
private static final int AFTER_CASED = 1;
- private static final int AFTER_VOWEL_WITH_ACCENT = 2;
+ private static final int AFTER_VOWEL_WITH_COMBINING_ACCENT = 2;
+ private static final int AFTER_VOWEL_WITH_PRECOMPOSED_ACCENT = 4;
// Data generated by prototype code, see
// https://icu.unicode.org/design/case/greek-upper
@@ -1421,14 +1422,17 @@
// Adding one only to the final vowel in a longer sequence
// (which does not occur in normal writing) would require lookahead.
// Set the same flag as for preserving an existing dialytika.
- if ((data & HAS_VOWEL) != 0 && (state & AFTER_VOWEL_WITH_ACCENT) != 0 &&
- (upper == 'Ι' || upper == 'Υ')) {
- data |= HAS_DIALYTIKA;
+ if ((data & HAS_VOWEL) != 0
+ && (state & (AFTER_VOWEL_WITH_PRECOMPOSED_ACCENT | AFTER_VOWEL_WITH_COMBINING_ACCENT)) != 0
+ && (upper == 'Ι' || upper == 'Υ')) {
+ data |= (state & AFTER_VOWEL_WITH_PRECOMPOSED_ACCENT) != 0 ? HAS_DIALYTIKA
+ : HAS_COMBINING_DIALYTIKA;
}
int numYpogegrammeni = 0; // Map each one to a trailing, spacing, capital iota.
if ((data & HAS_YPOGEGRAMMENI) != 0) {
numYpogegrammeni = 1;
}
+ final boolean hasPrecomposedAccent = (data & HAS_ACCENT) != 0;
// Skip combining diacritics after this Greek letter.
while (nextIndex < src.length()) {
int diacriticData = getDiacriticData(src.charAt(nextIndex));
@@ -1443,7 +1447,8 @@
}
}
if ((data & HAS_VOWEL_AND_ACCENT_AND_DIALYTIKA) == HAS_VOWEL_AND_ACCENT) {
- nextState |= AFTER_VOWEL_WITH_ACCENT;
+ nextState |= hasPrecomposedAccent ? AFTER_VOWEL_WITH_PRECOMPOSED_ACCENT
+ : AFTER_VOWEL_WITH_COMBINING_ACCENT;
}
// Map according to Greek rules.
boolean addTonos = false;
@@ -1454,7 +1459,7 @@
!isFollowedByCasedLetter(src, nextIndex)) {
// Keep disjunctive "or" with (only) a tonos.
// We use the same "word boundary" conditions as for the Final_Sigma test.
- if (i == nextIndex) {
+ if (hasPrecomposedAccent) {
upper = 'Ή'; // Preserve the precomposed form.
} else {
addTonos = true;
diff --git a/android_icu4j/src/main/java/android/icu/impl/CharacterPropertiesImpl.java b/android_icu4j/src/main/java/android/icu/impl/CharacterPropertiesImpl.java
index ffbef2c..29353e4 100644
--- a/android_icu4j/src/main/java/android/icu/impl/CharacterPropertiesImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/CharacterPropertiesImpl.java
@@ -76,6 +76,14 @@
EmojiProps.INSTANCE.addPropertyStarts(incl);
break;
}
+ case UCharacterProperty.SRC_IDSU:
+ // New in Unicode 15.1 for just two characters.
+ incl.add(0x2FFE);
+ incl.add(0x2FFF + 1);
+ break;
+ case UCharacterProperty.SRC_ID_COMPAT_MATH:
+ UCharacterProperty.mathCompat_addPropertyStarts(incl);
+ break;
default:
throw new IllegalStateException("getInclusions(unknown src " + src + ")");
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java b/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java
index f160e78..4d30687 100644
--- a/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java
+++ b/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java
@@ -247,7 +247,7 @@
Number result = null;
if (sawNumber) {
num = negative ? num * (-1) : num;
- result = Long.valueOf(num);
+ result = num;
parsePosition.setIndex(base + offset);
}
return result;
diff --git a/android_icu4j/src/main/java/android/icu/impl/DayPeriodRules.java b/android_icu4j/src/main/java/android/icu/impl/DayPeriodRules.java
index 56d77f4..348a5dd 100644
--- a/android_icu4j/src/main/java/android/icu/impl/DayPeriodRules.java
+++ b/android_icu4j/src/main/java/android/icu/impl/DayPeriodRules.java
@@ -246,7 +246,7 @@
* Get a DayPeriodRules object given a locale.
* If data hasn't been loaded, it will be loaded for all locales at once.
* @param locale locale for which the DayPeriodRules object is requested.
- * @return a DayPeriodRules object for `locale`.
+ * @return a DayPeriodRules object for {@code locale}.
*/
public static DayPeriodRules getInstance(ULocale locale) {
String localeCode = locale.getBaseName();
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUBinary.java b/android_icu4j/src/main/java/android/icu/impl/ICUBinary.java
index b1f9a72..f5f0c6a 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUBinary.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUBinary.java
@@ -225,11 +225,15 @@
abstract void addBaseNamesInFolder(String folder, String suffix, Set<String> names);
}
+ // BEGIN Android-changed: Map file only once during ICUBinary initialization. Attempt to fix
+ // some apps not seeing metazones.res file. See b/339899412.
private static final class SingleDataFile extends DataFile {
+ private final ByteBuffer bytes;
private final File path;
SingleDataFile(String item, File path) {
super(item);
+ this.bytes = mapFile(path);
this.path = path;
}
@Override
@@ -240,12 +244,13 @@
@Override
ByteBuffer getData(String requestedPath) {
if (requestedPath.equals(itemPath)) {
- return mapFile(path);
+ return bytes.duplicate();
} else {
return null;
}
}
-
+ // END Android-changed: Map file only once during ICUBinary initialization. Attempt to fix
+ // some apps not seeing metazones.res files in b/339899412.
@Override
void addBaseNamesInFolder(String folder, String suffix, Set<String> names) {
if (itemPath.length() > folder.length() + suffix.length() &&
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUCurrencyDisplayInfoProvider.java b/android_icu4j/src/main/java/android/icu/impl/ICUCurrencyDisplayInfoProvider.java
index 5ba1c2d..44705a6 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUCurrencyDisplayInfoProvider.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUCurrencyDisplayInfoProvider.java
@@ -13,6 +13,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
+import java.util.Set;
import android.icu.impl.CurrencyData.CurrencyDisplayInfo;
import android.icu.impl.CurrencyData.CurrencyDisplayInfoProvider;
@@ -210,18 +211,19 @@
public String getPluralName(String isoCode, String pluralKey ) {
StandardPlural plural = StandardPlural.orNullFromString(pluralKey);
String[] pluralsData = fetchPluralsData(isoCode);
+ Set<String> pluralKeys = fetchUnitPatterns().keySet();
// See http://unicode.org/reports/tr35/#Currencies, especially the fallback rule.
String result = null;
if (plural != null) {
result = pluralsData[1 + plural.ordinal()];
}
- if (result == null && fallback) {
+ if (result == null && ( fallback || pluralKeys.contains(pluralKey) ) ) {
// First fall back to the "other" plural variant
// Note: If plural is already "other", this fallback is benign
result = pluralsData[1 + StandardPlural.OTHER.ordinal()];
}
- if (result == null && fallback) {
+ if (result == null && ( fallback || pluralKeys.contains(pluralKey) ) ) {
// If that fails, fall back to the display name
FormattingData formattingData = fetchFormattingData(isoCode);
result = formattingData.displayName;
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java b/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
index 8113915..3ed55c0 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
@@ -274,7 +274,7 @@
}
}
} catch (Throwable t) {
- //System.err.println("Error in - " + new Integer(i).toString()
+ //System.err.println("Error in - " + i
// + " - " + t.toString());
// ignore the err - just skip that resource
}
@@ -402,11 +402,11 @@
}
/**
- * Locates the resource specified by `path` in this resource bundle (performing any necessary fallback and
- * following any aliases) and calls the specified `sink`'s `put()` method with that resource. Then walks the
- * bundle's parent chain, calling `put()` on the sink for each item in the parent chain.
+ * Locates the resource specified by {@code path} in this resource bundle (performing any necessary fallback
+ * and following any aliases) and calls the specified {@code sink}'s {@code put()} method with that resource.
+ * Then walks the bundle's parent chain, calling {@code put()} on the sink for each item in the parent chain.
* @param path The path of the desired resource
- * @param sink A `UResource.Sink` that gets called for each resource in the parent chain
+ * @param sink A {@code UResource.Sink} that gets called for each resource in the parent chain
*/
public void getAllItemsWithFallback(String path, UResource.Sink sink)
throws MissingResourceException {
@@ -435,16 +435,16 @@
}
/**
- * Locates the resource specified by `path` in this resource bundle (performing any necessary fallback and
+ * Locates the resource specified by {@code path} in this resource bundle (performing any necessary fallback and
* following any aliases) and, if the resource is a table resource, iterates over its immediate child resources (again,
- * following any aliases to get the individual resource values), and calls the specified `sink`'s `put()` method
- * for each child resource (passing it that resource's key and either its actual value or, if that value is an
+ * following any aliases to get the individual resource values), and calls the specified {@code sink}'s {@code put()}
+ * method for each child resource (passing it that resource's key and either its actual value or, if that value is an
* alias, the value you get by following the alias). Then walks back over the bundle's parent chain,
* similarly iterating over each parent table resource's child resources.
* Does not descend beyond one level of table children.
* @param path The path of the desired resource
- * @param sink A `UResource.Sink` that gets called for each child resource of the specified resource (and each child
- * of the resources in its parent chain).
+ * @param sink A {@code UResource.Sink} that gets called for each child resource of the specified resource (and each
+ * child of the resources in its parent chain).
*/
public void getAllChildrenWithFallback(final String path, final UResource.Sink sink)
throws MissingResourceException {
@@ -464,7 +464,43 @@
ReaderValue aliasedValue = new ReaderValue();
aliasedValue.reader = aliasedResourceImpl.wholeBundle.reader;
aliasedValue.res = aliasedResourceImpl.getResource();
- sink.put(key, aliasedValue, noFallback);
+
+ if (aliasedValue.getType() != TABLE) {
+ sink.put(key, aliasedValue, noFallback);
+ } else {
+ // if the resource we're aliasing over to is a table, the sink might iterate over its contents.
+ // If it does, it'll get only the things defined in the actual alias target, not the things
+ // the target inherits from its parent resources. So we walk the parent chain for the *alias target*,
+ // calling sink.put() for each of the parent tables we could be inheriting from. This means
+ // that sink.put() has to iterate over the children of multiple tables to get all of the inherited
+ // resource values, but it already has to do that to handle normal vertical inheritance.
+ int aliasedValueType = TABLE;
+ String tablePath = aliasPath.substring("/LOCALE/".length());
+ UResource.Key keyCopy = key.clone(); // sink.put() changes the key
+ sink.put(keyCopy, aliasedValue, noFallback);
+ while (aliasedValueType == TABLE && aliasedResource.getParent() != null) {
+ ICUResourceBundle newAliasedResource = aliasedResource.getParent().findWithFallback(tablePath);
+ if (newAliasedResource.key.equals(aliasedResource.key)) {
+ aliasedResource = newAliasedResource;
+ } else {
+ // the findWithFallback() call above might follow an alias. If it does, we'll get
+ // back the alias target at the wrong level (e.g., if we're in en_CA, we're calling
+ // findWithFallback() on en, and if it follows an alias, we get back the alias target
+ // in en, even if it also exists in en_CA). So we check the keys to see if we followed
+ // an alias, and if we did, we re-fetch the alias target from our original resource bundle
+ tablePath = tablePath.substring(0, tablePath.lastIndexOf('/'));
+ tablePath += "/" + newAliasedResource.key;
+ aliasedResource = ICUResourceBundle.this.findWithFallback(tablePath);
+ }
+ aliasedResourceImpl = (ICUResourceBundleImpl) aliasedResource;
+ aliasedValue = new ReaderValue();
+ aliasedValue.reader = aliasedResourceImpl.wholeBundle.reader;
+ aliasedValue.res = aliasedResourceImpl.getResource();
+ aliasedValueType = aliasedValue.getType(); // sink.put() messes up the value
+ keyCopy = key.clone(); // sink.put() messes up the key
+ sink.put(keyCopy, aliasedValue, noFallback);
+ }
+ }
} else {
sink.put(key, value, noFallback);
}
@@ -1293,7 +1329,7 @@
return result;
}
- private static String getParentLocaleID(String name, String origName, OpenType openType) {
+ public static String getParentLocaleID(String name, String origName, OpenType openType) {
// early out if the locale ID has a variant code or ends with _
if (name.endsWith("_") || !ULocale.getVariant(name).isEmpty()) {
int lastUnderbarPos = name.lastIndexOf('_');
diff --git a/android_icu4j/src/main/java/android/icu/impl/ICUService.java b/android_icu4j/src/main/java/android/icu/impl/ICUService.java
index d6ebaa4..e34ad4c 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ICUService.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ICUService.java
@@ -597,6 +597,8 @@
Factory f = lIter.previous();
f.updateVisibleIDs(mutableMap);
}
+ // Capture the return value in a local variable.
+ // Avoids returning an idcache value changed by another thread (could be null after clearCaches()).
Map<String, Factory> result = Collections.unmodifiableMap(mutableMap);
this.idcache = result;
return result;
diff --git a/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java b/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java
index 6ed1cad..2ed8790 100644
--- a/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java
+++ b/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java
@@ -20,19 +20,56 @@
private static Map<String, String> buildDefaultScriptTable() {
Map<String, String> t = new HashMap<>();
+ t.put("aaf", "Mlym");
+ t.put("aao", "Arab");
+ t.put("aat", "Grek");
t.put("ab", "Cyrl");
- t.put("abq", "Cyrl");
- t.put("adp", "Tibt");
+ t.put("abh", "Arab");
+ t.put("abl", "Rjng");
+ t.put("abv", "Arab");
+ t.put("acm", "Arab");
+ t.put("acq", "Arab");
+ t.put("acw", "Arab");
+ t.put("acx", "Arab");
+ t.put("adf", "Arab");
+ t.put("adx", "Tibt");
t.put("ady", "Cyrl");
t.put("ae", "Avst");
t.put("aeb", "Arab");
+ t.put("aec", "Arab");
+ t.put("aee", "Arab");
+ t.put("aeq", "Arab");
+ t.put("afb", "Arab");
+ t.put("agi", "Deva");
+ t.put("agj", "Ethi");
+ t.put("agx", "Cyrl");
+ t.put("ahg", "Ethi");
t.put("aho", "Ahom");
- t.put("ajt", "Arab");
+ t.put("ahr", "Deva");
+ t.put("aib", "Arab");
+ t.put("aij", "Hebr");
+ t.put("ain", "Kana");
+ t.put("aio", "Mymr");
+ t.put("aiq", "Arab");
t.put("akk", "Xsux");
+ t.put("akv", "Cyrl");
+ t.put("alk", "Laoo");
+ t.put("all", "Mlym");
+ t.put("alr", "Cyrl");
t.put("alt", "Cyrl");
+ t.put("alw", "Ethi");
t.put("am", "Ethi");
+ t.put("ams", "Jpan");
+ t.put("amw", "Syrc");
+ t.put("ani", "Cyrl");
+ t.put("anp", "Deva");
+ t.put("anr", "Deva");
+ t.put("anu", "Ethi");
+ t.put("aot", "Beng");
t.put("apc", "Arab");
t.put("apd", "Arab");
+ t.put("aph", "Deva");
+ t.put("aqc", "Cyrl");
t.put("ar", "Arab");
t.put("arc", "Armi");
t.put("arq", "Arab");
@@ -41,200 +78,525 @@
t.put("arz", "Arab");
t.put("as", "Beng");
t.put("ase", "Sgnw");
+ t.put("ask", "Arab");
+ t.put("atn", "Arab");
+ t.put("atv", "Cyrl");
+ t.put("auj", "Arab");
+ t.put("auz", "Arab");
t.put("av", "Cyrl");
+ t.put("avd", "Arab");
t.put("avl", "Arab");
t.put("awa", "Deva");
+ t.put("awn", "Ethi");
+ t.put("axm", "Armn");
+ t.put("ayh", "Arab");
+ t.put("ayl", "Arab");
+ t.put("ayn", "Arab");
+ t.put("ayp", "Arab");
t.put("az_IQ", "Arab");
t.put("az_IR", "Arab");
t.put("az_RU", "Cyrl");
+ t.put("azb", "Arab");
t.put("ba", "Cyrl");
t.put("bal", "Arab");
t.put("bap", "Deva");
t.put("bax", "Bamu");
+ t.put("bbl", "Geor");
t.put("bcq", "Ethi");
+ t.put("bdv", "Orya");
+ t.put("bdz", "Arab");
t.put("be", "Cyrl");
+ t.put("bee", "Deva");
t.put("bej", "Arab");
+ t.put("bfb", "Deva");
t.put("bfq", "Taml");
t.put("bft", "Arab");
+ t.put("bfu", "Tibt");
+ t.put("bfw", "Orya");
t.put("bfy", "Deva");
+ t.put("bfz", "Deva");
t.put("bg", "Cyrl");
t.put("bgc", "Deva");
+ t.put("bgd", "Deva");
t.put("bgn", "Arab");
+ t.put("bgp", "Arab");
+ t.put("bgq", "Deva");
+ t.put("bgw", "Deva");
t.put("bgx", "Grek");
+ t.put("bha", "Deva");
t.put("bhb", "Deva");
+ t.put("bhd", "Deva");
+ t.put("bhe", "Arab");
+ t.put("bhh", "Cyrl");
t.put("bhi", "Deva");
+ t.put("bhj", "Deva");
+ t.put("bhm", "Arab");
+ t.put("bhn", "Syrc");
t.put("bho", "Deva");
- t.put("bji", "Ethi");
+ t.put("bht", "Takr");
+ t.put("bhu", "Deva");
+ t.put("biy", "Deva");
+ t.put("bjf", "Syrc");
t.put("bjj", "Deva");
+ t.put("bjm", "Arab");
+ t.put("blk", "Mymr");
t.put("blt", "Tavt");
+ t.put("bmj", "Deva");
t.put("bn", "Beng");
+ t.put("bns", "Deva");
t.put("bo", "Tibt");
+ t.put("bph", "Cyrl");
+ t.put("bpx", "Deva");
t.put("bpy", "Beng");
t.put("bqi", "Arab");
t.put("bra", "Deva");
+ t.put("brb", "Khmr");
+ t.put("brd", "Deva");
t.put("brh", "Arab");
+ t.put("brk", "Arab");
+ t.put("brv", "Laoo");
t.put("brx", "Deva");
+ t.put("bsh", "Arab");
+ t.put("bsk", "Arab");
t.put("bsq", "Bass");
t.put("bst", "Ethi");
+ t.put("btd", "Batk");
+ t.put("btm", "Batk");
t.put("btv", "Deva");
t.put("bua", "Cyrl");
+ t.put("bwe", "Mymr");
+ t.put("bxm", "Cyrl");
+ t.put("bxu", "Mong");
+ t.put("byh", "Deva");
t.put("byn", "Ethi");
+ t.put("byw", "Deva");
+ t.put("bzi", "Thai");
+ t.put("cbn", "Thai");
t.put("ccp", "Cakm");
+ t.put("cde", "Telu");
+ t.put("cdh", "Deva");
+ t.put("cdi", "Gujr");
+ t.put("cdj", "Deva");
+ t.put("cdm", "Deva");
+ t.put("cdo", "Hans");
+ t.put("cdz", "Beng");
t.put("ce", "Cyrl");
+ t.put("cgk", "Tibt");
+ t.put("chg", "Arab");
t.put("chm", "Cyrl");
t.put("chr", "Cher");
+ t.put("chx", "Deva");
+ t.put("cih", "Deva");
t.put("cja", "Arab");
+ t.put("cji", "Cyrl");
t.put("cjm", "Cham");
+ t.put("cjy", "Hans");
t.put("ckb", "Arab");
+ t.put("ckt", "Cyrl");
+ t.put("clh", "Arab");
+ t.put("clw", "Cyrl");
t.put("cmg", "Soyo");
+ t.put("cna", "Tibt");
+ t.put("cnp", "Hans");
+ t.put("cog", "Thai");
t.put("cop", "Copt");
+ t.put("cpg", "Grek");
t.put("cr", "Cans");
t.put("crh", "Cyrl");
+ t.put("crj", "Cans");
t.put("crk", "Cans");
t.put("crl", "Cans");
+ t.put("crm", "Cans");
+ t.put("csh", "Mymr");
+ t.put("csp", "Hans");
t.put("csw", "Cans");
t.put("ctd", "Pauc");
+ t.put("ctg", "Beng");
+ t.put("ctn", "Deva");
+ t.put("ctt", "Taml");
t.put("cu", "Cyrl");
+ t.put("cuu", "Lana");
t.put("cv", "Cyrl");
+ t.put("czh", "Hans");
+ t.put("czk", "Hebr");
+ t.put("daq", "Deva");
t.put("dar", "Cyrl");
t.put("dcc", "Arab");
- t.put("dgl", "Arab");
+ t.put("ddo", "Cyrl");
+ t.put("def", "Arab");
+ t.put("deh", "Arab");
+ t.put("der", "Beng");
+ t.put("dhi", "Deva");
+ t.put("dhn", "Gujr");
+ t.put("dho", "Deva");
+ t.put("dhw", "Deva");
+ t.put("dka", "Tibt");
+ t.put("dlg", "Cyrl");
t.put("dmf", "Medf");
+ t.put("dmk", "Arab");
+ t.put("dml", "Arab");
+ t.put("dng", "Cyrl");
+ t.put("dnu", "Mymr");
+ t.put("dnv", "Mymr");
t.put("doi", "Deva");
- t.put("drh", "Mong");
+ t.put("dox", "Ethi");
+ t.put("dre", "Tibt");
+ t.put("drq", "Deva");
t.put("drs", "Ethi");
+ t.put("dry", "Deva");
+ t.put("dso", "Orya");
t.put("dty", "Deva");
+ t.put("dub", "Gujr");
+ t.put("duh", "Deva");
+ t.put("dus", "Deva");
t.put("dv", "Thaa");
+ t.put("dwk", "Orya");
+ t.put("dwz", "Deva");
t.put("dz", "Tibt");
+ t.put("dzl", "Tibt");
+ t.put("ecr", "Grek");
+ t.put("ecy", "Cprt");
t.put("egy", "Egyp");
t.put("eky", "Kali");
t.put("el", "Grek");
+ t.put("emg", "Deva");
+ t.put("emu", "Deva");
+ t.put("enf", "Cyrl");
+ t.put("enh", "Cyrl");
+ t.put("era", "Taml");
t.put("esg", "Gonm");
+ t.put("esh", "Arab");
t.put("ett", "Ital");
+ t.put("eve", "Cyrl");
+ t.put("evn", "Cyrl");
t.put("fa", "Arab");
+ t.put("fay", "Arab");
+ t.put("faz", "Arab");
t.put("fia", "Arab");
+ t.put("fmu", "Deva");
t.put("fub", "Arab");
t.put("gan", "Hans");
+ t.put("gaq", "Orya");
+ t.put("gas", "Gujr");
+ t.put("gau", "Telu");
+ t.put("gbj", "Orya");
+ t.put("gbk", "Deva");
+ t.put("gbl", "Gujr");
t.put("gbm", "Deva");
t.put("gbz", "Arab");
+ t.put("gdb", "Orya");
+ t.put("gdo", "Cyrl");
+ t.put("gdx", "Deva");
t.put("gez", "Ethi");
- t.put("ggn", "Deva");
+ t.put("ggg", "Arab");
+ t.put("gha", "Arab");
+ t.put("ghe", "Deva");
+ t.put("ghr", "Arab");
+ t.put("ght", "Tibt");
+ t.put("gig", "Arab");
+ t.put("gin", "Cyrl");
t.put("gjk", "Arab");
t.put("gju", "Arab");
+ t.put("gld", "Cyrl");
+ t.put("glh", "Arab");
t.put("glk", "Arab");
t.put("gmv", "Ethi");
+ t.put("gmy", "Linb");
+ t.put("goe", "Tibt");
t.put("gof", "Ethi");
+ t.put("gok", "Deva");
t.put("gom", "Deva");
t.put("gon", "Telu");
t.put("got", "Goth");
+ t.put("gra", "Deva");
t.put("grc", "Cprt");
t.put("grt", "Beng");
+ t.put("gru", "Ethi");
t.put("gu", "Gujr");
t.put("gvr", "Deva");
t.put("gwc", "Arab");
+ t.put("gwf", "Arab");
t.put("gwt", "Arab");
+ t.put("gyo", "Deva");
+ t.put("gzi", "Arab");
t.put("ha_CM", "Arab");
t.put("ha_SD", "Arab");
+ t.put("hac", "Arab");
t.put("hak", "Hans");
+ t.put("har", "Ethi");
t.put("haz", "Arab");
+ t.put("hbo", "Hebr");
t.put("hdy", "Ethi");
t.put("he", "Hebr");
t.put("hi", "Deva");
+ t.put("hii", "Takr");
+ t.put("hit", "Xsux");
+ t.put("hkh", "Arab");
+ t.put("hlb", "Deva");
t.put("hlu", "Hluw");
t.put("hmd", "Plrd");
+ t.put("hmj", "Bopo");
+ t.put("hmq", "Bopo");
t.put("hnd", "Arab");
t.put("hne", "Deva");
t.put("hnj", "Hmnp");
+ t.put("hnj_AU", "Laoo");
+ t.put("hnj_CN", "Laoo");
+ t.put("hnj_FR", "Laoo");
+ t.put("hnj_GF", "Laoo");
+ t.put("hnj_LA", "Laoo");
+ t.put("hnj_MM", "Laoo");
+ t.put("hnj_SR", "Laoo");
+ t.put("hnj_TH", "Laoo");
+ t.put("hnj_VN", "Laoo");
t.put("hno", "Arab");
t.put("hoc", "Deva");
+ t.put("hoh", "Arab");
t.put("hoj", "Deva");
+ t.put("how", "Hani");
+ t.put("hoy", "Deva");
+ t.put("hpo", "Mymr");
+ t.put("hrt", "Syrc");
+ t.put("hrz", "Arab");
t.put("hsn", "Hans");
+ t.put("hss", "Arab");
+ t.put("htx", "Xsux");
+ t.put("hut", "Deva");
+ t.put("huy", "Hebr");
+ t.put("huz", "Cyrl");
t.put("hy", "Armn");
+ t.put("hyw", "Armn");
t.put("ii", "Yiii");
+ t.put("imy", "Lyci");
t.put("inh", "Cyrl");
+ t.put("int", "Mymr");
+ t.put("ior", "Ethi");
+ t.put("iru", "Taml");
+ t.put("isk", "Arab");
+ t.put("itk", "Hebr");
+ t.put("itl", "Cyrl");
t.put("iu", "Cans");
t.put("iw", "Hebr");
t.put("ja", "Jpan");
+ t.put("jad", "Arab");
+ t.put("jat", "Arab");
+ t.put("jbe", "Hebr");
+ t.put("jbn", "Arab");
+ t.put("jct", "Cyrl");
+ t.put("jda", "Tibt");
+ t.put("jdg", "Arab");
+ t.put("jdt", "Cyrl");
+ t.put("jee", "Deva");
+ t.put("jge", "Geor");
t.put("ji", "Hebr");
+ t.put("jje", "Hang");
+ t.put("jkm", "Mymr");
t.put("jml", "Deva");
+ t.put("jna", "Takr");
+ t.put("jnd", "Arab");
+ t.put("jnl", "Deva");
+ t.put("jns", "Deva");
+ t.put("jog", "Arab");
+ t.put("jpa", "Hebr");
+ t.put("jpr", "Hebr");
+ t.put("jrb", "Hebr");
+ t.put("jrb_MA", "Arab");
+ t.put("jul", "Deva");
+ t.put("jun", "Orya");
+ t.put("juy", "Orya");
+ t.put("jya", "Tibt");
+ t.put("jye", "Hebr");
t.put("ka", "Geor");
t.put("kaa", "Cyrl");
+ t.put("kap", "Cyrl");
t.put("kaw", "Kawi");
t.put("kbd", "Cyrl");
+ t.put("kbu", "Arab");
t.put("kby", "Arab");
+ t.put("kca", "Cyrl");
+ t.put("kdq", "Beng");
t.put("kdt", "Thai");
+ t.put("ket", "Cyrl");
+ t.put("kex", "Deva");
+ t.put("key", "Telu");
+ t.put("kfa", "Knda");
+ t.put("kfb", "Deva");
+ t.put("kfc", "Telu");
+ t.put("kfd", "Knda");
+ t.put("kfe", "Taml");
+ t.put("kfh", "Mlym");
+ t.put("kfi", "Taml");
+ t.put("kfk", "Deva");
+ t.put("kfm", "Arab");
+ t.put("kfp", "Deva");
+ t.put("kfq", "Deva");
t.put("kfr", "Deva");
+ t.put("kfs", "Deva");
+ t.put("kfx", "Deva");
t.put("kfy", "Deva");
+ t.put("kgj", "Deva");
+ t.put("kgy", "Deva");
t.put("khb", "Talu");
+ t.put("khf", "Thai");
+ t.put("khg", "Tibt");
t.put("khn", "Deva");
t.put("kht", "Mymr");
+ t.put("khv", "Cyrl");
t.put("khw", "Arab");
+ t.put("kif", "Deva");
+ t.put("kim", "Cyrl");
+ t.put("kip", "Deva");
t.put("kjg", "Laoo");
+ t.put("kjh", "Cyrl");
+ t.put("kjl", "Deva");
+ t.put("kjo", "Deva");
+ t.put("kjp", "Mymr");
+ t.put("kjt", "Thai");
t.put("kk", "Cyrl");
t.put("kk_AF", "Arab");
t.put("kk_CN", "Arab");
t.put("kk_IR", "Arab");
t.put("kk_MN", "Arab");
+ t.put("kkf", "Tibt");
+ t.put("kkh", "Lana");
+ t.put("kkt", "Deva");
+ t.put("kle", "Deva");
+ t.put("klj", "Arab");
+ t.put("klr", "Deva");
t.put("km", "Khmr");
+ t.put("kmj", "Deva");
+ t.put("kmz", "Arab");
t.put("kn", "Knda");
t.put("ko", "Kore");
t.put("koi", "Cyrl");
t.put("kok", "Deva");
+ t.put("kpt", "Cyrl");
+ t.put("kpy", "Cyrl");
+ t.put("kqd", "Syrc");
t.put("kqy", "Ethi");
+ t.put("kra", "Deva");
t.put("krc", "Cyrl");
+ t.put("krk", "Cyrl");
+ t.put("krr", "Khmr");
t.put("kru", "Deva");
+ t.put("krv", "Khmr");
t.put("ks", "Arab");
+ t.put("ksu", "Mymr");
+ t.put("ksw", "Mymr");
+ t.put("ksz", "Deva");
t.put("ktb", "Ethi");
+ t.put("ktl", "Arab");
+ t.put("ktp", "Plrd");
t.put("ku_LB", "Arab");
+ t.put("kuf", "Laoo");
t.put("kum", "Cyrl");
t.put("kv", "Cyrl");
+ t.put("kva", "Cyrl");
+ t.put("kvq", "Mymr");
+ t.put("kvt", "Mymr");
t.put("kvx", "Arab");
- t.put("kxc", "Ethi");
- t.put("kxl", "Deva");
+ t.put("kvy", "Kali");
+ t.put("kxf", "Mymr");
+ t.put("kxk", "Mymr");
t.put("kxm", "Thai");
t.put("kxp", "Arab");
t.put("ky", "Cyrl");
t.put("ky_CN", "Arab");
- t.put("kzh", "Arab");
+ t.put("kyu", "Kali");
+ t.put("kyv", "Deva");
+ t.put("kyw", "Deva");
t.put("lab", "Lina");
t.put("lad", "Hebr");
+ t.put("lae", "Deva");
t.put("lah", "Arab");
+ t.put("lbc", "Lisu");
t.put("lbe", "Cyrl");
+ t.put("lbf", "Deva");
+ t.put("lbj", "Tibt");
+ t.put("lbm", "Deva");
+ t.put("lbo", "Laoo");
+ t.put("lbr", "Deva");
t.put("lcp", "Thai");
t.put("lep", "Lepc");
t.put("lez", "Cyrl");
+ t.put("lhm", "Deva");
+ t.put("lhs", "Syrc");
t.put("lif", "Deva");
t.put("lis", "Lisu");
+ t.put("lkh", "Tibt");
t.put("lki", "Arab");
+ t.put("lmh", "Deva");
t.put("lmn", "Telu");
t.put("lo", "Laoo");
+ t.put("loy", "Deva");
+ t.put("lpo", "Plrd");
t.put("lrc", "Arab");
+ t.put("lrk", "Arab");
+ t.put("lrl", "Arab");
+ t.put("lsa", "Arab");
+ t.put("lsd", "Hebr");
+ t.put("lss", "Arab");
+ t.put("luk", "Tibt");
+ t.put("luu", "Deva");
+ t.put("luv", "Arab");
t.put("luz", "Arab");
t.put("lwl", "Thai");
+ t.put("lwm", "Thai");
+ t.put("lya", "Tibt");
t.put("lzh", "Hans");
t.put("mag", "Deva");
t.put("mai", "Deva");
t.put("man_GN", "Nkoo");
+ t.put("mby", "Arab");
t.put("mde", "Arab");
t.put("mdf", "Cyrl");
t.put("mdx", "Ethi");
+ t.put("mdy", "Ethi");
t.put("mfa", "Arab");
+ t.put("mfi", "Arab");
t.put("mgp", "Deva");
+ t.put("mhj", "Arab");
+ t.put("mid", "Mand");
+ t.put("mjl", "Deva");
+ t.put("mjq", "Mlym");
+ t.put("mjr", "Mlym");
+ t.put("mjt", "Deva");
+ t.put("mju", "Telu");
+ t.put("mjv", "Mlym");
+ t.put("mjz", "Deva");
t.put("mk", "Cyrl");
+ t.put("mkb", "Deva");
+ t.put("mke", "Deva");
t.put("mki", "Arab");
+ t.put("mkm", "Thai");
t.put("ml", "Mlym");
+ t.put("mlf", "Thai");
t.put("mn", "Cyrl");
t.put("mn_CN", "Mong");
t.put("mni", "Beng");
+ t.put("mnj", "Arab");
+ t.put("mns", "Cyrl");
t.put("mnw", "Mymr");
+ t.put("mpz", "Thai");
t.put("mr", "Deva");
+ t.put("mra", "Thai");
t.put("mrd", "Deva");
t.put("mrj", "Cyrl");
t.put("mro", "Mroo");
+ t.put("mrr", "Deva");
t.put("ms_CC", "Arab");
+ t.put("mtm", "Cyrl");
t.put("mtr", "Deva");
+ t.put("mud", "Cyrl");
+ t.put("muk", "Tibt");
+ t.put("mut", "Deva");
+ t.put("muv", "Taml");
+ t.put("muz", "Ethi");
+ t.put("mvf", "Mong");
t.put("mvy", "Arab");
+ t.put("mvz", "Ethi");
t.put("mwr", "Deva");
+ t.put("mwt", "Mymr");
t.put("mww", "Hmnp");
t.put("my", "Mymr");
t.put("mym", "Ethi");
@@ -242,134 +604,407 @@
t.put("myz", "Mand");
t.put("mzn", "Arab");
t.put("nan", "Hans");
+ t.put("nao", "Deva");
+ t.put("ncd", "Deva");
+ t.put("ncq", "Laoo");
+ t.put("ndf", "Cyrl");
t.put("ne", "Deva");
+ t.put("neg", "Cyrl");
+ t.put("neh", "Tibt");
+ t.put("nei", "Xsux");
t.put("new", "Deva");
+ t.put("ngt", "Laoo");
+ t.put("nio", "Cyrl");
+ t.put("nit", "Telu");
+ t.put("niv", "Cyrl");
+ t.put("nli", "Arab");
+ t.put("nlm", "Arab");
+ t.put("nlx", "Deva");
+ t.put("nmm", "Deva");
t.put("nnp", "Wcho");
t.put("nod", "Lana");
t.put("noe", "Deva");
+ t.put("nog", "Cyrl");
+ t.put("noi", "Deva");
t.put("non", "Runr");
+ t.put("nos", "Yiii");
+ t.put("npb", "Tibt");
t.put("nqo", "Nkoo");
+ t.put("nsd", "Yiii");
+ t.put("nsf", "Yiii");
t.put("nsk", "Cans");
t.put("nst", "Tnsa");
+ t.put("nsv", "Yiii");
+ t.put("nty", "Yiii");
+ t.put("ntz", "Arab");
+ t.put("nwc", "Newa");
+ t.put("nwx", "Deva");
+ t.put("nyl", "Thai");
+ t.put("nyq", "Arab");
+ t.put("oaa", "Cyrl");
+ t.put("oac", "Cyrl");
+ t.put("oar", "Syrc");
+ t.put("oav", "Geor");
+ t.put("obm", "Phnx");
+ t.put("obr", "Mymr");
+ t.put("odk", "Arab");
+ t.put("oht", "Xsux");
t.put("oj", "Cans");
t.put("ojs", "Cans");
+ t.put("okm", "Hang");
+ t.put("oko", "Hani");
+ t.put("okz", "Khmr");
+ t.put("ola", "Deva");
+ t.put("ole", "Tibt");
+ t.put("omk", "Cyrl");
+ t.put("omp", "Mtei");
+ t.put("omr", "Modi");
+ t.put("oon", "Deva");
t.put("or", "Orya");
+ t.put("ort", "Telu");
t.put("oru", "Arab");
+ t.put("orv", "Cyrl");
t.put("os", "Cyrl");
t.put("osa", "Osge");
+ t.put("osc", "Ital");
+ t.put("osi", "Java");
t.put("ota", "Arab");
+ t.put("otb", "Tibt");
t.put("otk", "Orkh");
- t.put("oui", "Ougr");
+ t.put("oty", "Gran");
t.put("pa", "Guru");
t.put("pa_PK", "Arab");
t.put("pal", "Phli");
+ t.put("paq", "Cyrl");
+ t.put("pbt", "Arab");
+ t.put("pcb", "Khmr");
+ t.put("pce", "Mymr");
+ t.put("pcf", "Mlym");
+ t.put("pcg", "Mlym");
+ t.put("pch", "Deva");
+ t.put("pci", "Deva");
+ t.put("pcj", "Telu");
+ t.put("peg", "Orya");
t.put("peo", "Xpeo");
+ t.put("pgd", "Khar");
+ t.put("pgg", "Deva");
+ t.put("pgl", "Ogam");
+ t.put("pgn", "Ital");
+ t.put("phd", "Deva");
+ t.put("phk", "Mymr");
t.put("phl", "Arab");
t.put("phn", "Phnx");
+ t.put("pho", "Laoo");
+ t.put("phr", "Arab");
+ t.put("pht", "Thai");
+ t.put("phv", "Arab");
+ t.put("phw", "Deva");
+ t.put("pi", "Sinh");
t.put("pka", "Brah");
+ t.put("pkr", "Mlym");
+ t.put("plk", "Arab");
+ t.put("pll", "Mymr");
+ t.put("pmh", "Brah");
t.put("pnt", "Grek");
- t.put("ppa", "Deva");
t.put("pra", "Khar");
+ t.put("prc", "Arab");
t.put("prd", "Arab");
+ t.put("prt", "Thai");
+ t.put("prx", "Arab");
t.put("ps", "Arab");
+ t.put("psh", "Arab");
+ t.put("psi", "Arab");
+ t.put("pst", "Arab");
+ t.put("pum", "Deva");
+ t.put("pwo", "Mymr");
+ t.put("pwr", "Deva");
+ t.put("pww", "Thai");
+ t.put("pyx", "Mymr");
+ t.put("qxq", "Arab");
+ t.put("raa", "Deva");
+ t.put("rab", "Deva");
+ t.put("raf", "Deva");
+ t.put("rah", "Beng");
t.put("raj", "Deva");
+ t.put("rav", "Deva");
+ t.put("rbb", "Mymr");
+ t.put("rdb", "Arab");
+ t.put("rei", "Orya");
t.put("rhg", "Rohg");
- t.put("rif", "Tfng");
+ t.put("rji", "Deva");
t.put("rjs", "Deva");
+ t.put("rka", "Khmr");
+ t.put("rki", "Mymr");
t.put("rkt", "Beng");
+ t.put("rmi", "Armn");
t.put("rmt", "Arab");
+ t.put("rmz", "Mymr");
+ t.put("rom_BG", "Cyrl");
+ t.put("rsk", "Cyrl");
+ t.put("rtw", "Deva");
t.put("ru", "Cyrl");
t.put("rue", "Cyrl");
+ t.put("rut", "Cyrl");
+ t.put("rwr", "Deva");
t.put("ryu", "Kana");
t.put("sa", "Deva");
t.put("sah", "Cyrl");
+ t.put("sam", "Samr");
t.put("sat", "Olck");
t.put("saz", "Saur");
+ t.put("sbn", "Arab");
+ t.put("sbu", "Tibt");
t.put("sck", "Deva");
t.put("scl", "Arab");
+ t.put("scl_IN", "Arab");
+ t.put("scp", "Deva");
+ t.put("sct", "Laoo");
+ t.put("scu", "Takr");
+ t.put("scx", "Grek");
t.put("sd", "Arab");
t.put("sd_IN", "Deva");
+ t.put("sdb", "Arab");
+ t.put("sdf", "Arab");
+ t.put("sdg", "Arab");
t.put("sdh", "Arab");
+ t.put("sds", "Arab");
+ t.put("sel", "Cyrl");
+ t.put("sfm", "Plrd");
t.put("sga", "Ogam");
+ t.put("sgh", "Cyrl");
+ t.put("sgj", "Deva");
+ t.put("sgr", "Arab");
+ t.put("sgt", "Tibt");
t.put("sgw", "Ethi");
+ t.put("sgy", "Arab");
+ t.put("shd", "Arab");
t.put("shi", "Tfng");
+ t.put("shm", "Arab");
t.put("shn", "Mymr");
t.put("shu", "Arab");
+ t.put("shv", "Arab");
t.put("si", "Sinh");
+ t.put("sia", "Cyrl");
+ t.put("sip", "Tibt");
+ t.put("siy", "Arab");
+ t.put("siz", "Arab");
+ t.put("sjd", "Cyrl");
+ t.put("sjp", "Deva");
+ t.put("sjt", "Cyrl");
+ t.put("skb", "Thai");
+ t.put("skj", "Deva");
t.put("skr", "Arab");
+ t.put("smh", "Yiii");
t.put("smp", "Samr");
+ t.put("smu", "Khmr");
+ t.put("smy", "Arab");
+ t.put("soa", "Tavt");
t.put("sog", "Sogd");
+ t.put("soi", "Deva");
t.put("sou", "Thai");
+ t.put("spt", "Tibt");
+ t.put("spv", "Orya");
+ t.put("sqo", "Arab");
+ t.put("sqq", "Laoo");
+ t.put("sqt", "Arab");
t.put("sr", "Cyrl");
t.put("srb", "Sora");
+ t.put("srh", "Arab");
t.put("srx", "Deva");
+ t.put("srz", "Arab");
+ t.put("ssh", "Arab");
+ t.put("sss", "Laoo");
+ t.put("sts", "Arab");
+ t.put("stv", "Ethi");
+ t.put("sty", "Cyrl");
+ t.put("suz", "Deva");
+ t.put("sva", "Geor");
t.put("swb", "Arab");
+ t.put("swi", "Hani");
t.put("swv", "Deva");
+ t.put("syc", "Syrc");
t.put("syl", "Beng");
+ t.put("syn", "Syrc");
t.put("syr", "Syrc");
+ t.put("syw", "Deva");
t.put("ta", "Taml");
+ t.put("tab", "Cyrl");
t.put("taj", "Deva");
+ t.put("tbk", "Tagb");
+ t.put("tcn", "Tibt");
+ t.put("tco", "Mymr");
+ t.put("tcx", "Taml");
t.put("tcy", "Knda");
+ t.put("tda", "Tfng");
+ t.put("tdb", "Deva");
t.put("tdd", "Tale");
t.put("tdg", "Deva");
t.put("tdh", "Deva");
t.put("te", "Telu");
+ t.put("tes", "Java");
t.put("tg", "Cyrl");
t.put("tg_PK", "Arab");
+ t.put("tge", "Deva");
+ t.put("tgf", "Tibt");
t.put("th", "Thai");
+ t.put("the", "Deva");
+ t.put("thf", "Deva");
+ t.put("thi", "Tale");
t.put("thl", "Deva");
+ t.put("thm", "Thai");
t.put("thq", "Deva");
t.put("thr", "Deva");
+ t.put("ths", "Deva");
t.put("ti", "Ethi");
t.put("tig", "Ethi");
+ t.put("tij", "Deva");
+ t.put("tin", "Cyrl");
+ t.put("tjl", "Mymr");
+ t.put("tjo", "Arab");
+ t.put("tkb", "Deva");
+ t.put("tks", "Arab");
t.put("tkt", "Deva");
+ t.put("tmr", "Syrc");
+ t.put("tnv", "Cakm");
+ t.put("tov", "Arab");
+ t.put("tpu", "Khmr");
+ t.put("tra", "Arab");
+ t.put("trg", "Hebr");
+ t.put("trm", "Arab");
t.put("trw", "Arab");
t.put("tsd", "Grek");
- t.put("tsf", "Deva");
t.put("tsj", "Tibt");
t.put("tt", "Cyrl");
+ t.put("tth", "Laoo");
+ t.put("tto", "Laoo");
t.put("tts", "Thai");
+ t.put("tvn", "Mymr");
+ t.put("twm", "Deva");
t.put("txg", "Tang");
t.put("txo", "Toto");
+ t.put("tyr", "Tavt");
t.put("tyv", "Cyrl");
+ t.put("ude", "Cyrl");
+ t.put("udg", "Mlym");
t.put("udi", "Aghb");
t.put("udm", "Cyrl");
t.put("ug", "Arab");
t.put("ug_KZ", "Cyrl");
t.put("ug_MN", "Cyrl");
t.put("uga", "Ugar");
+ t.put("ugh", "Cyrl");
+ t.put("ugo", "Thai");
t.put("uk", "Cyrl");
+ t.put("uki", "Orya");
+ t.put("ulc", "Cyrl");
t.put("unr", "Beng");
t.put("unr_NP", "Deva");
t.put("unx", "Beng");
t.put("ur", "Arab");
+ t.put("urk", "Thai");
+ t.put("ush", "Arab");
+ t.put("uum", "Grek");
t.put("uz_AF", "Arab");
t.put("uz_CN", "Cyrl");
+ t.put("uzs", "Arab");
+ t.put("vaa", "Taml");
+ t.put("vaf", "Arab");
+ t.put("vah", "Deva");
t.put("vai", "Vaii");
+ t.put("vas", "Deva");
+ t.put("vav", "Deva");
+ t.put("vay", "Deva");
+ t.put("vgr", "Arab");
+ t.put("vmd", "Knda");
+ t.put("vmh", "Arab");
t.put("wal", "Ethi");
+ t.put("wbk", "Arab");
t.put("wbq", "Telu");
t.put("wbr", "Deva");
+ t.put("wlo", "Arab");
+ t.put("wme", "Deva");
+ t.put("wne", "Arab");
t.put("wni", "Arab");
t.put("wsg", "Gong");
+ t.put("wsv", "Arab");
t.put("wtm", "Deva");
t.put("wuu", "Hans");
+ t.put("xal", "Cyrl");
+ t.put("xan", "Ethi");
+ t.put("xas", "Cyrl");
t.put("xco", "Chrs");
t.put("xcr", "Cari");
+ t.put("xdq", "Cyrl");
+ t.put("xhe", "Arab");
+ t.put("xhm", "Khmr");
+ t.put("xis", "Orya");
+ t.put("xka", "Arab");
+ t.put("xkc", "Arab");
+ t.put("xkj", "Arab");
+ t.put("xkp", "Arab");
t.put("xlc", "Lyci");
t.put("xld", "Lydi");
+ t.put("xly", "Elym");
t.put("xmf", "Geor");
t.put("xmn", "Mani");
t.put("xmr", "Merc");
t.put("xna", "Narb");
t.put("xnr", "Deva");
+ t.put("xpg", "Grek");
+ t.put("xpi", "Ogam");
+ t.put("xpm", "Cyrl");
t.put("xpr", "Prti");
+ t.put("xrm", "Cyrl");
+ t.put("xrn", "Cyrl");
t.put("xsa", "Sarb");
t.put("xsr", "Deva");
+ t.put("xub", "Taml");
+ t.put("xuj", "Taml");
+ t.put("xve", "Ital");
+ t.put("xvi", "Arab");
+ t.put("xwo", "Cyrl");
+ t.put("xzh", "Marc");
+ t.put("yai", "Cyrl");
+ t.put("ybh", "Deva");
+ t.put("ybi", "Deva");
+ t.put("ydg", "Arab");
+ t.put("yea", "Mlym");
+ t.put("yej", "Grek");
+ t.put("yeu", "Telu");
+ t.put("ygp", "Plrd");
+ t.put("yhd", "Hebr");
t.put("yi", "Hebr");
+ t.put("yig", "Yiii");
+ t.put("yih", "Hebr");
+ t.put("yiv", "Yiii");
+ t.put("ykg", "Cyrl");
+ t.put("yna", "Plrd");
+ t.put("ynk", "Cyrl");
+ t.put("yoi", "Jpan");
+ t.put("yoy", "Thai");
+ t.put("yrk", "Cyrl");
+ t.put("ysd", "Yiii");
+ t.put("ysn", "Yiii");
+ t.put("ysp", "Yiii");
+ t.put("ysr", "Cyrl");
+ t.put("ysy", "Plrd");
+ t.put("yud", "Hebr");
t.put("yue", "Hant");
t.put("yue_CN", "Hans");
+ t.put("yug", "Cyrl");
+ t.put("yux", "Cyrl");
+ t.put("ywq", "Plrd");
+ t.put("ywu", "Plrd");
+ t.put("zau", "Tibt");
+ t.put("zba", "Arab");
+ t.put("zch", "Hani");
t.put("zdj", "Arab");
+ t.put("zeh", "Hani");
+ t.put("zen", "Tfng");
+ t.put("zgb", "Hani");
t.put("zgh", "Tfng");
+ t.put("zgm", "Hani");
+ t.put("zgn", "Hani");
t.put("zh", "Hans");
t.put("zh_AU", "Hant");
t.put("zh_BN", "Hant");
@@ -386,8 +1021,20 @@
t.put("zh_TW", "Hant");
t.put("zh_US", "Hant");
t.put("zh_VN", "Hant");
+ t.put("zhd", "Hani");
t.put("zhx", "Nshu");
+ t.put("zko", "Cyrl");
t.put("zkt", "Kits");
+ t.put("zkz", "Cyrl");
+ t.put("zlj", "Hani");
+ t.put("zln", "Hani");
+ t.put("zlq", "Hani");
+ t.put("zqe", "Hani");
+ t.put("zrp", "Hebr");
+ t.put("zum", "Arab");
+ t.put("zyg", "Hani");
+ t.put("zyn", "Hani");
+ t.put("zzj", "Hani");
return Collections.unmodifiableMap(t);
}
@@ -442,6 +1089,7 @@
t.put("en_GM", "en_001");
t.put("en_GY", "en_001");
t.put("en_HK", "en_001");
+ t.put("en_ID", "en_001");
t.put("en_IE", "en_001");
t.put("en_IL", "en_001");
t.put("en_IM", "en_001");
@@ -514,6 +1162,7 @@
t.put("es_EC", "es_419");
t.put("es_GT", "es_419");
t.put("es_HN", "es_419");
+ t.put("es_JP", "es_419");
t.put("es_MX", "es_419");
t.put("es_NI", "es_419");
t.put("es_PA", "es_419");
@@ -533,6 +1182,9 @@
t.put("kk_Arab", "root");
t.put("ks_Deva", "root");
t.put("ku_Arab", "root");
+ t.put("kxv_Deva", "root");
+ t.put("kxv_Orya", "root");
+ t.put("kxv_Telu", "root");
t.put("ky_Arab", "root");
t.put("ky_Latn", "root");
t.put("ml_Arab", "root");
@@ -541,6 +1193,7 @@
t.put("ms_Arab", "root");
t.put("nb", "no");
t.put("nn", "no");
+ t.put("no_NO", "no");
t.put("pa_Arab", "root");
t.put("pt_AO", "pt_PT");
t.put("pt_CH", "pt_PT");
diff --git a/android_icu4j/src/main/java/android/icu/impl/LocaleIDParser.java b/android_icu4j/src/main/java/android/icu/impl/LocaleIDParser.java
index a9f3186..10e0665 100644
--- a/android_icu4j/src/main/java/android/icu/impl/LocaleIDParser.java
+++ b/android_icu4j/src/main/java/android/icu/impl/LocaleIDParser.java
@@ -367,6 +367,15 @@
}
}
+ // There are no strict limitation of the syntax of variant in the legacy
+ // locale format. If the locale is constructed from unicode_locale_id
+ // as defined in UTS35, then we know each unicode_variant_subtag
+ // could have max length of 8 ((alphanum{5,8} | digit alphanum{3})
+ // 179 would allow 20 unicode_variant_subtag with sep in the
+ // unicode_locale_id
+ // 8*20 + 1*(20-1) = 179
+ private static final int MAX_VARIANTS_LENGTH = 179;
+
/**
* Advance index past variant, and accumulate normalized variant in buffer. This ignores
* the codepage information from POSIX ids. Index must be immediately after the country
@@ -434,10 +443,12 @@
c = UNDERSCORE;
}
append(c);
+ if (buffer.length() - oldBlen > MAX_VARIANTS_LENGTH) {
+ throw new IllegalArgumentException("variants is too long");
+ }
}
}
--index; // unget
-
return oldBlen;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/Norm2AllModes.java b/android_icu4j/src/main/java/android/icu/impl/Norm2AllModes.java
index 701f311..1b1c6f8 100644
--- a/android_icu4j/src/main/java/android/icu/impl/Norm2AllModes.java
+++ b/android_icu4j/src/main/java/android/icu/impl/Norm2AllModes.java
@@ -328,6 +328,9 @@
public static Norm2AllModes getNFKC_CFInstance() {
return getInstanceFromSingleton(NFKC_CFSingleton.INSTANCE);
}
+ public static Norm2AllModes getNFKC_SCFInstance() {
+ return getInstanceFromSingleton(NFKC_SCFSingleton.INSTANCE);
+ }
// For use in properties APIs.
public static Normalizer2WithImpl getN2WithImpl(int index) {
switch(index) {
@@ -404,4 +407,7 @@
private static final class NFKC_CFSingleton {
private static final Norm2AllModesSingleton INSTANCE=new Norm2AllModesSingleton("nfkc_cf");
}
+ private static final class NFKC_SCFSingleton {
+ private static final Norm2AllModesSingleton INSTANCE=new Norm2AllModesSingleton("nfkc_scf");
+ }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java b/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java
index 4673179..83abdf3 100644
--- a/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java
+++ b/android_icu4j/src/main/java/android/icu/impl/OlsonTimeZone.java
@@ -477,7 +477,7 @@
/**
* Construct from a resource bundle
* @param top the top-level zoneinfo resource bundle. This is used
- * to lookup the rule that `res' may refer to, if there is one.
+ * to lookup the rule that {@code res} may refer to, if there is one.
* @param res the resource bundle of the zone to be constructed
* @param id time zone ID
*/
diff --git a/android_icu4j/src/main/java/android/icu/impl/PropsVectors.java b/android_icu4j/src/main/java/android/icu/impl/PropsVectors.java
index 99f52fd..490d48a 100644
--- a/android_icu4j/src/main/java/android/icu/impl/PropsVectors.java
+++ b/android_icu4j/src/main/java/android/icu/impl/PropsVectors.java
@@ -380,7 +380,7 @@
// sort the properties vectors to find unique vector values
Integer[] indexArray = new Integer[rows];
for (int i = 0; i < rows; ++i) {
- indexArray[i] = Integer.valueOf(columns * i);
+ indexArray[i] = columns * i;
}
Arrays.sort(indexArray, new Comparator<Integer>() {
diff --git a/android_icu4j/src/main/java/android/icu/impl/RuleCharacterIterator.java b/android_icu4j/src/main/java/android/icu/impl/RuleCharacterIterator.java
index 5b7c16a..b8b3ced 100644
--- a/android_icu4j/src/main/java/android/icu/impl/RuleCharacterIterator.java
+++ b/android_icu4j/src/main/java/android/icu/impl/RuleCharacterIterator.java
@@ -219,11 +219,11 @@
* }
* iterator.setPos(pos);
*
- * @param p a position object previously returned by getPos(),
+ * @param p a position object previously returned by {@code getPos()},
* or null. If not null, it will be updated and returned. If
* null, a new position object will be allocated and returned.
- * @return a position object which may be passed to setPos(),
- * either `p,' or if `p' == null, a newly-allocated object
+ * @return a position object which may be passed to setPos(), either
+ * {@code p}, or if {@code p} == null, a newly-allocated object
*/
public Position getPos(Position p) {
if (p == null) {
diff --git a/android_icu4j/src/main/java/android/icu/impl/UCaseProps.java b/android_icu4j/src/main/java/android/icu/impl/UCaseProps.java
index 1c6875b..82d948b 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UCaseProps.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UCaseProps.java
@@ -264,34 +264,6 @@
* - for k include the Kelvin sign
*/
public final void addCaseClosure(int c, UnicodeSet set) {
- /*
- * Hardcode the case closure of i and its relatives and ignore the
- * data file data for these characters.
- * The Turkic dotless i and dotted I with their case mapping conditions
- * and case folding option make the related characters behave specially.
- * This code matches their closure behavior to their case folding behavior.
- */
-
- switch(c) {
- case 0x49:
- /* regular i and I are in one equivalence class */
- set.add(0x69);
- return;
- case 0x69:
- set.add(0x49);
- return;
- case 0x130:
- /* dotted I is in a class with <0069 0307> (for canonical equivalence with <0049 0307>) */
- set.add(iDot);
- return;
- case 0x131:
- /* dotless i is in a class by itself */
- return;
- default:
- /* otherwise use the data file data */
- break;
- }
-
int props=trie.get(c);
if(!propsHasException(props)) {
if(getTypeFromProps(props)!=NONE) {
@@ -306,19 +278,41 @@
* c has exceptions, so there may be multiple simple and/or
* full case mappings. Add them all.
*/
- int excOffset0, excOffset=getExceptionsOffset(props);
- int closureOffset;
+ int excOffset=getExceptionsOffset(props);
int excWord=exceptions.charAt(excOffset++);
- int index, closureLength, fullLength, length;
+ int excOffset0=excOffset;
- excOffset0=excOffset;
+ // Hardcode the case closure of i and its relatives and ignore the
+ // data file data for these characters.
+ // The Turkic dotless i and dotted I with their case mapping conditions
+ // and case folding option make the related characters behave specially.
+ // This code matches their closure behavior to their case folding behavior.
+ if ((excWord&EXC_CONDITIONAL_FOLD) != 0) {
+ // These characters have Turkic case foldings. Hardcode their closure.
+ if (c == 0x49) {
+ // Regular i and I are in one equivalence class.
+ set.add(0x69);
+ return;
+ } else if (c == 0x130) {
+ // Dotted I is in a class with <0069 0307>
+ // (for canonical equivalence with <0049 0307>).
+ set.add(iDot);
+ return;
+ }
+ } else if (c == 0x69) {
+ set.add(0x49);
+ return;
+ } else if (c == 0x131) {
+ // Dotless i is in a class by itself.
+ return;
+ }
/* add all simple case mappings */
- for(index=EXC_LOWER; index<=EXC_TITLE; ++index) {
+ for(int index=EXC_LOWER; index<=EXC_TITLE; ++index) {
if(hasSlot(excWord, index)) {
excOffset=excOffset0;
- c=getSlotValue(excWord, index, excOffset);
- set.add(c);
+ int mapping=getSlotValue(excWord, index, excOffset);
+ set.add(mapping);
}
}
if(hasSlot(excWord, EXC_DELTA)) {
@@ -328,6 +322,7 @@
}
/* get the closure string pointer & length */
+ int closureOffset, closureLength;
if(hasSlot(excWord, EXC_CLOSURE)) {
excOffset=excOffset0;
long value=getSlotValueAndOffset(excWord, EXC_CLOSURE, excOffset);
@@ -342,7 +337,7 @@
if(hasSlot(excWord, EXC_FULL_MAPPINGS)) {
excOffset=excOffset0;
long value=getSlotValueAndOffset(excWord, EXC_FULL_MAPPINGS, excOffset);
- fullLength=(int)value;
+ int fullLength=(int)value;
/* start of full case mapping strings */
excOffset=(int)(value>>32)+1;
@@ -354,7 +349,7 @@
fullLength>>=4;
/* add the full case folding string */
- length=fullLength&0xf;
+ int length=fullLength&0xf;
if(length!=0) {
set.add(exceptions.substring(excOffset, excOffset+length));
excOffset+=length;
@@ -371,9 +366,104 @@
/* add each code point in the closure string */
int limit=closureOffset+closureLength;
- for(index=closureOffset; index<limit; index+=UTF16.getCharCount(c)) {
- c=exceptions.codePointAt(index);
- set.add(c);
+ for(int index=closureOffset; index<limit; index+=UTF16.getCharCount(c)) {
+ int mapping=exceptions.codePointAt(index);
+ set.add(mapping);
+ }
+ }
+ }
+
+ public final void addSimpleCaseClosure(int c, UnicodeSet set) {
+ int props=trie.get(c);
+ if(!propsHasException(props)) {
+ if(getTypeFromProps(props)!=NONE) {
+ /* add the one simple case mapping, no matter what type it is */
+ int delta=getDelta(props);
+ if(delta!=0) {
+ set.add(c+delta);
+ }
+ }
+ } else {
+ // c has exceptions. Add the mappings relevant for scf=Simple_Case_Folding.
+ int excOffset=getExceptionsOffset(props);
+ int excWord=exceptions.charAt(excOffset++);
+ int excOffset0=excOffset;
+
+ // Hardcode the case closure of i and its relatives and ignore the
+ // data file data for these characters, like in ucase_addCaseClosure().
+ if ((excWord&EXC_CONDITIONAL_FOLD) != 0) {
+ // These characters have Turkic case foldings. Hardcode their closure.
+ if (c == 0x49) {
+ // Regular i and I are in one equivalence class.
+ set.add(0x69);
+ return;
+ } else if (c == 0x130) {
+ // For scf=Simple_Case_Folding, dotted I is in a class by itself.
+ return;
+ }
+ } else if (c == 0x69) {
+ set.add(0x49);
+ return;
+ } else if (c == 0x131) {
+ // Dotless i is in a class by itself.
+ return;
+ }
+
+ // Add all simple case mappings.
+ for(int index=EXC_LOWER; index<=EXC_TITLE; ++index) {
+ if(hasSlot(excWord, index)) {
+ excOffset=excOffset0;
+ int mapping=getSlotValue(excWord, index, excOffset);
+ set.add(mapping);
+ }
+ }
+ if(hasSlot(excWord, EXC_DELTA)) {
+ excOffset=excOffset0;
+ int delta=getSlotValue(excWord, EXC_DELTA, excOffset);
+ int mapping = (excWord&EXC_DELTA_IS_NEGATIVE)==0 ? c+delta : c-delta;
+ set.add(mapping);
+ }
+
+ /* get the closure string pointer & length */
+ int closureOffset, closureLength;
+ if(hasSlot(excWord, EXC_CLOSURE)) {
+ excOffset=excOffset0;
+ long value=getSlotValueAndOffset(excWord, EXC_CLOSURE, excOffset);
+ closureLength=(int)value&CLOSURE_MAX_LENGTH; /* higher bits are reserved */
+ closureOffset=(int)(value>>32)+1; /* behind this slot, unless there are full case mappings */
+ } else {
+ closureLength=0;
+ closureOffset=0;
+ }
+
+ // Skip the full case mappings.
+ if(closureLength > 0 && hasSlot(excWord, EXC_FULL_MAPPINGS)) {
+ excOffset=excOffset0;
+ long value=getSlotValueAndOffset(excWord, EXC_FULL_MAPPINGS, excOffset);
+ int fullLength=(int)value;
+
+ /* start of full case mapping strings */
+ excOffset=(int)(value>>32)+1;
+
+ fullLength&=0xffff; /* bits 16 and higher are reserved */
+
+ // Skip all 4 full case mappings.
+ excOffset+=fullLength&FULL_LOWER;
+ fullLength>>=4;
+ excOffset+=fullLength&0xf;
+ fullLength>>=4;
+ excOffset+=fullLength&0xf;
+ fullLength>>=4;
+ excOffset+=fullLength;
+
+ closureOffset=excOffset; /* behind full case mappings */
+ }
+
+ // Add each code point in the closure string whose scf maps back to c.
+ int limit=closureOffset+closureLength;
+ for(int index=closureOffset; index<limit; index+=UTF16.getCharCount(c)) {
+ int mapping=exceptions.codePointAt(index);
+ set.add(mapping);
}
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java b/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
index 822f399..3aba5b2 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
@@ -12,11 +12,14 @@
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.EnumSet;
import java.util.Iterator;
import java.util.MissingResourceException;
import android.icu.lang.UCharacter;
import android.icu.lang.UCharacter.HangulSyllableType;
+import android.icu.lang.UCharacter.IdentifierStatus;
+import android.icu.lang.UCharacter.IdentifierType;
import android.icu.lang.UCharacter.NumericType;
import android.icu.lang.UCharacterCategory;
import android.icu.lang.UProperty;
@@ -110,8 +113,10 @@
public static final int SRC_INSC=13;
public static final int SRC_VO=14;
public static final int SRC_EMOJI=15;
+ public static final int SRC_IDSU=16;
+ public static final int SRC_ID_COMPAT_MATH=17;
/** One more than the highest UPropertySource (SRC_) constant. */
- public static final int SRC_COUNT=16;
+ public static final int SRC_COUNT=18;
private static final class LayoutProps {
private static final class IsAcceptable implements ICUBinary.Authenticate {
@@ -378,6 +383,54 @@
}
}
+ /** Ranges (start/limit pairs) of ID_Compat_Math_Continue (only), from UCD PropList.txt. */
+ private static final int[] ID_COMPAT_MATH_CONTINUE = {
+ 0x00B2, 0x00B3 + 1,
+ 0x00B9, 0x00B9 + 1,
+ 0x2070, 0x2070 + 1,
+ 0x2074, 0x207E + 1,
+ 0x2080, 0x208E + 1
+ };
+
+ /** ID_Compat_Math_Start characters, from UCD PropList.txt. */
+ private static final int[] ID_COMPAT_MATH_START = {
+ 0x2202,
+ 0x2207,
+ 0x221E,
+ 0x1D6C1,
+ 0x1D6DB,
+ 0x1D6FB,
+ 0x1D715,
+ 0x1D735,
+ 0x1D74F,
+ 0x1D76F,
+ 0x1D789,
+ 0x1D7A9,
+ 0x1D7C3
+ };
+
+ private class MathCompatBinaryProperty extends BinaryProperty {
+ int which;
+ MathCompatBinaryProperty(int which) {
+ super(SRC_ID_COMPAT_MATH);
+ this.which=which;
+ }
+ @Override
+ boolean contains(int c) {
+ if (which == UProperty.ID_COMPAT_MATH_CONTINUE) {
+ for (int i = 0; i < ID_COMPAT_MATH_CONTINUE.length; i += 2) {
+ if (c < ID_COMPAT_MATH_CONTINUE[i]) { return false; } // below range start
+ if (c < ID_COMPAT_MATH_CONTINUE[i + 1]) { return true; } // below range limit
+ }
+ }
+ if (c < ID_COMPAT_MATH_START[0]) { return false; } // fastpath for common scripts
+ for (int startChar : ID_COMPAT_MATH_START) {
+ if (c == startChar) { return true; }
+ }
+ return false;
+ }
+ }
+
BinaryProperty[] binProps={
/*
* Binary-property implementations must be in order of corresponding UProperty,
@@ -569,6 +622,15 @@
new EmojiBinaryProperty(UProperty.RGI_EMOJI_TAG_SEQUENCE),
new EmojiBinaryProperty(UProperty.RGI_EMOJI_ZWJ_SEQUENCE),
new EmojiBinaryProperty(UProperty.RGI_EMOJI),
+ new BinaryProperty(SRC_IDSU) { // IDS_UNARY_OPERATOR
+ // New in Unicode 15.1 for just two characters.
+ @Override
+ boolean contains(int c) {
+ return 0x2FFE<=c && c<=0x2FFF;
+ }
+ },
+ new MathCompatBinaryProperty(UProperty.ID_COMPAT_MATH_START),
+ new MathCompatBinaryProperty(UProperty.ID_COMPAT_MATH_CONTINUE),
};
public boolean hasBinaryProperty(int c, int which) {
@@ -806,6 +868,18 @@
return LayoutProps.INSTANCE.maxVoValue;
}
},
+ new IntProperty(SRC_PROPSVEC) { // IDENTIFIER_STATUS
+ @Override
+ int getValue(int c) {
+ int value = getAdditional(c, 2) >>> ID_TYPE_SHIFT;
+ return value >= ID_TYPE_ALLOWED_MIN ?
+ IdentifierStatus.ALLOWED.ordinal() : IdentifierStatus.RESTRICTED.ordinal();
+ }
+ @Override
+ int getMaxValue(int which) {
+ return IdentifierStatus.ALLOWED.ordinal();
+ }
+ },
};
public int getIntPropertyValue(int c, int which) {
@@ -879,6 +953,7 @@
} else {
switch(which) {
case UProperty.SCRIPT_EXTENSIONS:
+ case UProperty.IDENTIFIER_TYPE:
return SRC_PROPSVEC;
default:
return SRC_NONE; /* undefined */
@@ -1386,20 +1461,73 @@
/*
* Properties in vector word 2
* Bits
- * 31..26 unused since ICU 70 added uemoji.icu;
- * in ICU 57..69 stored emoji properties
+ * 31..26 ICU 75: Identifier_Type bit set
+ * ICU 70..74: unused
+ * ICU 57..69: emoji properties; moved to uemoji.icu in ICU 70
* 25..20 Line Break
* 19..15 Sentence Break
* 14..10 Word Break
* 9.. 5 Grapheme Cluster Break
* 4.. 0 Decomposition Type
*/
- //ivate static final int PROPS_2_EXTENDED_PICTOGRAPHIC=26; // ICU 62..69
- //ivate static final int PROPS_2_EMOJI_COMPONENT = 27; // ICU 60..69
- //ivate static final int PROPS_2_EMOJI = 28; // ICU 57..69
- //ivate static final int PROPS_2_EMOJI_PRESENTATION = 29; // ICU 57..69
- //ivate static final int PROPS_2_EMOJI_MODIFIER = 30; // ICU 57..69
- //ivate static final int PROPS_2_EMOJI_MODIFIER_BASE = 31; // ICU 57..69
+
+ // https://www.unicode.org/reports/tr39/#Identifier_Status_and_Type
+ // The Identifier_Type maps each code point to a *set* of one or more values.
+ // Some can be combined with others, some can only occur alone.
+ // Exclusion & Limited_Use are combinable bits, but cannot occur together.
+ // We use this forbidden combination for enumerated values.
+ // We use 6 bits for all possible combinations.
+ // If more combinable values are added, then we need to use more bits.
+ //
+ // We do not store separate data for Identifier_Status:
+ // We can derive that from the encoded Identifier_Type via a simple range check.
+
+ // vate static final int ID_TYPE_MASK = 0xfc000000;
+ private static final int ID_TYPE_SHIFT = 26;
+
+ // A high bit for use in idTypeToEncoded[] but not used in the data
+ private static final int ID_TYPE_BIT = 0x80;
+
+ // Combinable bits
+ private static final int ID_TYPE_EXCLUSION = 0x20;
+ private static final int ID_TYPE_LIMITED_USE = 0x10;
+ private static final int ID_TYPE_UNCOMMON_USE = 8;
+ private static final int ID_TYPE_TECHNICAL = 4;
+ private static final int ID_TYPE_OBSOLETE = 2;
+ private static final int ID_TYPE_NOT_XID = 1;
+
+ // Exclusive values
+ private static final int ID_TYPE_NOT_CHARACTER = 0;
+
+ // Forbidden bit combination used for enumerating other exclusive values
+ private static final int ID_TYPE_FORBIDDEN = ID_TYPE_EXCLUSION | ID_TYPE_LIMITED_USE; // 0x30
+ private static final int ID_TYPE_DEPRECATED = ID_TYPE_FORBIDDEN; // 0x30
+ private static final int ID_TYPE_DEFAULT_IGNORABLE = ID_TYPE_FORBIDDEN + 1; // 0x31
+ private static final int ID_TYPE_NOT_NFKC = ID_TYPE_FORBIDDEN + 2; // 0x32
+
+ private static final int ID_TYPE_ALLOWED_MIN = ID_TYPE_FORBIDDEN + 0xc; // 0x3c
+ private static final int ID_TYPE_INCLUSION = ID_TYPE_FORBIDDEN + 0xe; // 0x3e
+ private static final int ID_TYPE_RECOMMENDED = ID_TYPE_FORBIDDEN + 0xf; // 0x3f
+
+ /**
+ * Maps UIdentifierType to encoded bits.
+ * When UPROPS_ID_TYPE_BIT is set, then use "&" to test whether the value bit is set.
+ * When UPROPS_ID_TYPE_BIT is not set, then compare ("==") the array value with the data value.
+ */
+ private static final int[] idTypeToEncoded = {
+ ID_TYPE_NOT_CHARACTER,
+ ID_TYPE_DEPRECATED,
+ ID_TYPE_DEFAULT_IGNORABLE,
+ ID_TYPE_NOT_NFKC,
+ ID_TYPE_BIT | ID_TYPE_NOT_XID,
+ ID_TYPE_BIT | ID_TYPE_EXCLUSION,
+ ID_TYPE_BIT | ID_TYPE_OBSOLETE,
+ ID_TYPE_BIT | ID_TYPE_TECHNICAL,
+ ID_TYPE_BIT | ID_TYPE_UNCOMMON_USE,
+ ID_TYPE_BIT | ID_TYPE_LIMITED_USE,
+ ID_TYPE_INCLUSION,
+ ID_TYPE_RECOMMENDED
+ };
private static final int LB_MASK = 0x03f00000;
private static final int LB_SHIFT = 20;
@@ -1506,7 +1634,7 @@
private static final class IsAcceptable implements ICUBinary.Authenticate {
@Override
public boolean isDataVersionAcceptable(byte version[]) {
- return version[0] == 7;
+ return version[0] == 8;
}
}
private static final int DATA_FORMAT = 0x5550726F; // "UPro"
@@ -1665,6 +1793,73 @@
return LayoutProps.INSTANCE.addPropertyStarts(src, set);
}
+ static void mathCompat_addPropertyStarts(UnicodeSet set) {
+ // range limits
+ for (int c : ID_COMPAT_MATH_CONTINUE) {
+ set.add(c);
+ }
+ // single characters
+ for (int c : ID_COMPAT_MATH_START) {
+ set.add(c);
+ set.add(c + 1);
+ }
+ }
+
+ public boolean hasIDType(int c, int typeIndex) {
+ if (typeIndex < 0 || typeIndex >= idTypeToEncoded.length) {
+ return false;
+ }
+ int encodedType = idTypeToEncoded[typeIndex];
+ int value = getAdditional(c, 2) >>> ID_TYPE_SHIFT;
+ if ((encodedType & ID_TYPE_BIT) != 0) {
+ return value < ID_TYPE_FORBIDDEN && (value & encodedType) != 0;
+ } else {
+ return value == encodedType;
+ }
+ }
+
+ public boolean hasIDType(int c, IdentifierType type) {
+ return hasIDType(c, type.ordinal());
+ }
+
+ private static void maybeAddType(int value, int bit, IdentifierType t,
+ EnumSet<IdentifierType> types) {
+ if ((value & bit) != 0) {
+ types.add(t);
+ }
+ }
+
+ public int getIDTypes(int c, EnumSet<IdentifierType> types) {
+ types.clear();
+ int value = getAdditional(c, 2) >>> ID_TYPE_SHIFT;;
+ if ((value & ID_TYPE_FORBIDDEN) == ID_TYPE_FORBIDDEN || value == ID_TYPE_NOT_CHARACTER) {
+ // single value
+ IdentifierType t;
+ switch (value) {
+ case ID_TYPE_NOT_CHARACTER: t = IdentifierType.NOT_CHARACTER; break;
+ case ID_TYPE_DEPRECATED: t = IdentifierType.DEPRECATED; break;
+ case ID_TYPE_DEFAULT_IGNORABLE: t = IdentifierType.DEFAULT_IGNORABLE; break;
+ case ID_TYPE_NOT_NFKC: t = IdentifierType.NOT_NFKC; break;
+ case ID_TYPE_INCLUSION: t = IdentifierType.INCLUSION; break;
+ case ID_TYPE_RECOMMENDED: t = IdentifierType.RECOMMENDED; break;
+ default:
+ throw new IllegalStateException(
+ String.format("unknown IdentifierType data value 0x%02x", value));
+ }
+ types.add(t);
+ return 1;
+ } else {
+ // one or more combinable bits
+ maybeAddType(value, ID_TYPE_NOT_XID, IdentifierType.NOT_XID, types);
+ maybeAddType(value, ID_TYPE_EXCLUSION, IdentifierType.EXCLUSION, types);
+ maybeAddType(value, ID_TYPE_OBSOLETE, IdentifierType.OBSOLETE, types);
+ maybeAddType(value, ID_TYPE_TECHNICAL, IdentifierType.TECHNICAL, types);
+ maybeAddType(value, ID_TYPE_UNCOMMON_USE, IdentifierType.UNCOMMON_USE, types);
+ maybeAddType(value, ID_TYPE_LIMITED_USE, IdentifierType.LIMITED_USE, types);
+ return types.size();
+ }
+ }
+
// This static initializer block must be placed after
// other static member initialization
static {
diff --git a/android_icu4j/src/main/java/android/icu/impl/UTS46.java b/android_icu4j/src/main/java/android/icu/impl/UTS46.java
index 327e5ee..e8f68c4 100644
--- a/android_icu4j/src/main/java/android/icu/impl/UTS46.java
+++ b/android_icu4j/src/main/java/android/icu/impl/UTS46.java
@@ -301,14 +301,6 @@
}
return length;
}
- // Some non-ASCII characters are equivalent to sequences with
- // non-LDH ASCII characters. To find them:
- // grep disallowed_STD3_valid IdnaMappingTable.txt (or uts46.txt)
- private static boolean
- isNonASCIIDisallowedSTD3Valid(int c) {
- return c==0x2260 || c==0x226E || c==0x226F;
- }
-
// Replace the label in dest with the label string, if the label was modified.
// If label==dest then the label was modified in-place and labelLength
@@ -422,10 +414,7 @@
}
} else {
oredChars|=c;
- if(disallowNonLDHDot && isNonASCIIDisallowedSTD3Valid(c)) {
- addLabelError(info, Error.DISALLOWED);
- labelString.setCharAt(i, '\ufffd');
- } else if(c==0xfffd) {
+ if(c==0xfffd) {
addLabelError(info, Error.DISALLOWED);
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/ZoneMeta.java b/android_icu4j/src/main/java/android/icu/impl/ZoneMeta.java
index ae5c271..c96c991 100644
--- a/android_icu4j/src/main/java/android/icu/impl/ZoneMeta.java
+++ b/android_icu4j/src/main/java/android/icu/impl/ZoneMeta.java
@@ -14,14 +14,12 @@
package android.icu.impl;
import java.lang.ref.SoftReference;
-import java.text.ParsePosition;
import java.util.Collections;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeSet;
-import android.icu.text.NumberFormat;
import android.icu.util.Output;
import android.icu.util.SimpleTimeZone;
import android.icu.util.TimeZone;
@@ -70,7 +68,7 @@
systemZones = REF_SYSTEM_ZONES.get();
}
if (systemZones == null) {
- Set<String> systemIDs = new TreeSet<String>();
+ Set<String> systemIDs = new TreeSet<>();
String[] allIDs = getZoneIDs();
for (String id : allIDs) {
// exclude Etc/Unknown
@@ -80,7 +78,7 @@
systemIDs.add(id);
}
systemZones = Collections.unmodifiableSet(systemIDs);
- REF_SYSTEM_ZONES = new SoftReference<Set<String>>(systemZones);
+ REF_SYSTEM_ZONES = new SoftReference<>(systemZones);
}
return systemZones;
}
@@ -97,7 +95,7 @@
canonicalSystemZones = REF_CANONICAL_SYSTEM_ZONES.get();
}
if (canonicalSystemZones == null) {
- Set<String> canonicalSystemIDs = new TreeSet<String>();
+ Set<String> canonicalSystemIDs = new TreeSet<>();
String[] allIDs = getZoneIDs();
for (String id : allIDs) {
// exclude Etc/Unknown
@@ -110,7 +108,7 @@
}
}
canonicalSystemZones = Collections.unmodifiableSet(canonicalSystemIDs);
- REF_CANONICAL_SYSTEM_ZONES = new SoftReference<Set<String>>(canonicalSystemZones);
+ REF_CANONICAL_SYSTEM_ZONES = new SoftReference<>(canonicalSystemZones);
}
return canonicalSystemZones;
}
@@ -129,7 +127,7 @@
canonicalSystemLocationZones = REF_CANONICAL_SYSTEM_LOCATION_ZONES.get();
}
if (canonicalSystemLocationZones == null) {
- Set<String> canonicalSystemLocationIDs = new TreeSet<String>();
+ Set<String> canonicalSystemLocationIDs = new TreeSet<>();
String[] allIDs = getZoneIDs();
for (String id : allIDs) {
// exclude Etc/Unknown
@@ -145,7 +143,7 @@
}
}
canonicalSystemLocationZones = Collections.unmodifiableSet(canonicalSystemLocationIDs);
- REF_CANONICAL_SYSTEM_LOCATION_ZONES = new SoftReference<Set<String>>(canonicalSystemLocationZones);
+ REF_CANONICAL_SYSTEM_LOCATION_ZONES = new SoftReference<>(canonicalSystemLocationZones);
}
return canonicalSystemLocationZones;
}
@@ -183,7 +181,7 @@
}
// Filter by region/rawOffset
- Set<String> result = new TreeSet<String>();
+ Set<String> result = new TreeSet<>();
for (String id : baseSet) {
if (region != null) {
String r = getRegion(id);
@@ -340,9 +338,9 @@
return zoneIdx;
}
- private static ICUCache<String, String> CANONICAL_ID_CACHE = new SimpleCache<String, String>();
- private static ICUCache<String, String> REGION_CACHE = new SimpleCache<String, String>();
- private static ICUCache<String, Boolean> SINGLE_COUNTRY_CACHE = new SimpleCache<String, Boolean>();
+ private static ICUCache<String, String> CANONICAL_ID_CACHE = new SimpleCache<>();
+ private static ICUCache<String, String> REGION_CACHE = new SimpleCache<>();
+ private static ICUCache<String, Boolean> SINGLE_COUNTRY_CACHE = new SimpleCache<>();
public static String getCanonicalCLDRID(TimeZone tz) {
if (tz instanceof OlsonTimeZone) {
@@ -354,7 +352,7 @@
/**
* Return the canonical id for this tzid defined by CLDR, which might be
* the id itself. If the given tzid is not known, return null.
- *
+ *
* Note: This internal API supports all known system IDs and "Etc/Unknown" (which is
* NOT a system ID).
*/
@@ -421,6 +419,33 @@
}
/**
+ * Returns primary IANA zone ID for the input zone ID. When input zone ID
+ * is not known, this method returns null.
+ *
+ * @param tzid An input zone ID.
+ * @return A primary IANA zone ID equivalent to the input zone ID.
+ */
+ public static String getIanaID(String tzid) {
+ // First, get CLDR canonical ID
+ String canonicalID = getCanonicalCLDRID(tzid);
+ if (canonicalID == null) {
+ return null;
+ }
+ // Find IANA mapping if any.
+ UResourceBundle keyTypeData = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,
+ "keyTypeData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
+ UResourceBundle ianaMap = keyTypeData.get("ianaMap");
+ UResourceBundle ianaTzMap = ianaMap.get("timezone");
+ try {
+ return ianaTzMap.getString(canonicalID.replace('/', ':'));
+ } catch (MissingResourceException e) {
+ // No IANA zone ID mapping. In this case, ianaId set by getCanonicalCLDRID()
+ // is also a primary IANA id.
+ return canonicalID;
+ }
+ }
+
+ /**
* Return the region code for this tzid.
* If tzid is not a system zone ID, this method returns null.
*/
@@ -477,7 +502,7 @@
if (singleZone == null) {
Set<String> ids = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL_LOCATION, country, null);
assert(ids.size() >= 1);
- singleZone = Boolean.valueOf(ids.size() <= 1);
+ singleZone = ids.size() <= 1;
SINGLE_COUNTRY_CACHE.put(tzid, singleZone);
}
@@ -622,8 +647,7 @@
// fields[1] - hour / 5-bit
// fields[2] - min / 6-bit
// fields[3] - sec / 6-bit
- Integer key = Integer.valueOf(
- fields[0] * (fields[1] | fields[2] << 5 | fields[3] << 11));
+ Integer key = fields[0] * (fields[1] | fields[2] << 5 | fields[3] << 11);
return CUSTOM_ZONE_CACHE.getInstance(key, fields);
}
return null;
@@ -655,66 +679,25 @@
* @return Returns true when the given custom id is valid.
*/
static boolean parseCustomID(String id, int[] fields) {
- NumberFormat numberFormat = null;
-
if (id != null && id.length() > kGMT_ID.length() &&
- id.toUpperCase(Locale.ENGLISH).startsWith(kGMT_ID)) {
- ParsePosition pos = new ParsePosition(kGMT_ID.length());
+ id.substring(0, 3).equalsIgnoreCase(kGMT_ID)) {
int sign = 1;
int hour = 0;
int min = 0;
int sec = 0;
- if (id.charAt(pos.getIndex()) == 0x002D /*'-'*/) {
+ int[] pos = new int[1];
+ pos[0] = kGMT_ID.length();
+ if (id.charAt(pos[0]) == 0x002D /*'-'*/) {
sign = -1;
- } else if (id.charAt(pos.getIndex()) != 0x002B /*'+'*/) {
+ } else if (id.charAt(pos[0]) != 0x002B /*'+'*/) {
return false;
}
- pos.setIndex(pos.getIndex() + 1);
-
- numberFormat = NumberFormat.getInstance();
- numberFormat.setParseIntegerOnly(true);
-
- // Look for either hh:mm, hhmm, or hh
- int start = pos.getIndex();
-
- Number n = numberFormat.parse(id, pos);
- if (pos.getIndex() == start) {
- return false;
- }
- hour = n.intValue();
-
- if (pos.getIndex() < id.length()){
- if (pos.getIndex() - start > 2
- || id.charAt(pos.getIndex()) != 0x003A /*':'*/) {
- return false;
- }
- // hh:mm
- pos.setIndex(pos.getIndex() + 1);
- int oldPos = pos.getIndex();
- n = numberFormat.parse(id, pos);
- if ((pos.getIndex() - oldPos) != 2) {
- // must be 2 digits
- return false;
- }
- min = n.intValue();
- if (pos.getIndex() < id.length()) {
- if (id.charAt(pos.getIndex()) != 0x003A /*':'*/) {
- return false;
- }
- // [:ss]
- pos.setIndex(pos.getIndex() + 1);
- oldPos = pos.getIndex();
- n = numberFormat.parse(id, pos);
- if (pos.getIndex() != id.length()
- || (pos.getIndex() - oldPos) != 2) {
- return false;
- }
- sec = n.intValue();
- }
- } else {
- // Supported formats are below -
- //
+ pos[0]++;
+ int start = pos[0];
+ hour = Utility.parseNumber(id, pos, 10);
+ if (pos[0] == id.length()) {
+ // Handle the following cases
// HHmmss
// Hmmss
// HHmm
@@ -722,27 +705,57 @@
// HH
// H
- int length = pos.getIndex() - start;
- if (length <= 0 || 6 < length) {
- // invalid length
- return false;
- }
+ // Get all digits
+ // Should be 1 to 6 digits.
+ int length = pos[0] - start;
switch (length) {
- case 1:
- case 2:
+ case 1: // H
+ case 2: // HH
// already set to hour
break;
- case 3:
- case 4:
+ case 3: // Hmm
+ case 4: // HHmm
min = hour % 100;
hour /= 100;
break;
- case 5:
- case 6:
+ case 5: // Hmmss
+ case 6: // HHmmss
sec = hour % 100;
min = (hour/100) % 100;
hour /= 10000;
break;
+ default:
+ // invalid range
+ return false;
+ }
+ } else {
+ // Handle the following cases
+ // HH:mm:ss
+ // H:mm:ss
+ // HH:mm
+ // H:mm
+ if (pos[0] - start < 1 || pos[0] - start > 2 || id.charAt(pos[0]) != 0x003A /*':'*/) {
+ return false;
+ }
+ pos[0]++; // skip : after H
+ if (id.length() == pos[0]) {
+ return false;
+ }
+ start = pos[0];
+ min = Utility.parseNumber(id, pos, 10);
+ if (pos[0] - start != 2) {
+ return false;
+ }
+ if (id.length() > pos[0]) {
+ if (id.charAt(pos[0]) != 0x003A /*':'*/) {
+ return false;
+ }
+ pos[0]++; // skip : after mm
+ start = pos[0];
+ sec = Utility.parseNumber(id, pos, 10);
+ if (pos[0] - start != 2 || id.length() > pos[0]) {
+ return false;
+ }
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/breakiter/BurmeseBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/BurmeseBreakEngine.java
index 6b0bfbc..4aecc01 100644
--- a/android_icu4j/src/main/java/android/icu/impl/breakiter/BurmeseBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/BurmeseBreakEngine.java
@@ -202,7 +202,7 @@
// Did we find a word on this iteration? If so, push it on the break stack
if (wordLength > 0) {
- foundBreaks.push(Integer.valueOf(current + wordLength));
+ foundBreaks.push(current + wordLength);
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/breakiter/KhmerBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/KhmerBreakEngine.java
index f746b98..6bbaa9f 100644
--- a/android_icu4j/src/main/java/android/icu/impl/breakiter/KhmerBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/KhmerBreakEngine.java
@@ -210,7 +210,7 @@
// Did we find a word on this iteration? If so, push it on the break stack
if (wordLength > 0) {
- foundBreaks.push(Integer.valueOf(current + wordLength));
+ foundBreaks.push(current + wordLength);
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/breakiter/LaoBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/LaoBreakEngine.java
index 9db413d..22b4343 100644
--- a/android_icu4j/src/main/java/android/icu/impl/breakiter/LaoBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/LaoBreakEngine.java
@@ -210,7 +210,7 @@
// Did we find a word on this iteration? If so, push it on the break stack
if (wordLength > 0) {
- foundBreaks.push(Integer.valueOf(current + wordLength));
+ foundBreaks.push(current + wordLength);
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/breakiter/MlBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/MlBreakEngine.java
index 957c8d3..c4fb1d3 100644
--- a/android_icu4j/src/main/java/android/icu/impl/breakiter/MlBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/MlBreakEngine.java
@@ -9,17 +9,17 @@
import static android.icu.impl.CharacterIteration.next32;
import static android.icu.impl.CharacterIteration.previous32;
+import java.text.CharacterIterator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
import android.icu.impl.ICUData;
import android.icu.text.UnicodeSet;
import android.icu.util.UResourceBundle;
import android.icu.util.UResourceBundleIterator;
-import java.text.CharacterIterator;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.HashMap;
-
enum ModelIndex {
kUWStart(0), kBWStart(6), kTWStart(9);
private final int value;
diff --git a/android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java
index 9d663bd..4f32d46 100644
--- a/android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java
+++ b/android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java
@@ -254,7 +254,7 @@
// Did we find a word on this iteration? If so, push it on the break stack
if (wordLength > 0) {
- foundBreaks.push(Integer.valueOf(current + wordLength));
+ foundBreaks.push(current + wordLength);
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/coll/CollationBuilder.java b/android_icu4j/src/main/java/android/icu/impl/coll/CollationBuilder.java
index 219d37e..e576392 100644
--- a/android_icu4j/src/main/java/android/icu/impl/coll/CollationBuilder.java
+++ b/android_icu4j/src/main/java/android/icu/impl/coll/CollationBuilder.java
@@ -25,6 +25,7 @@
import android.icu.text.Normalizer2;
import android.icu.text.UnicodeSet;
import android.icu.text.UnicodeSetIterator;
+import android.icu.util.ICUInputTooLongException;
import android.icu.util.ULocale;
/**
@@ -866,10 +867,21 @@
return ce32;
}
+ // ICU-22517
+ // This constant defines a limit for the addOnlyClosure to return
+ // error, to avoid taking a long time for canonical closure expansion.
+ // Please let us know if you have a reasonable use case that needed
+ // for a practical Collation rule that needs to increase this limit.
+ // This value is needed for compiling a rule with eight Hangul syllables such as
+ // "&a=bì«Šì«Šì«Šì«Šì«Šì«Šì«Šì«Š" without error, which should be more than realistic
+ // usage.
+ static private int kClosureLoopLimit = 6560;
+
private int addOnlyClosure(CharSequence nfdPrefix, CharSequence nfdString,
long[] newCEs, int newCEsLength, int ce32) {
// Map from canonically equivalent input to the CEs. (But not from the all-NFD input.)
// TODO: make CanonicalIterator work with CharSequence, or maybe change arguments here to String
+ int loop = 0;
if(nfdPrefix.length() == 0) {
CanonicalIterator stringIter = new CanonicalIterator(nfdString.toString());
String prefix = "";
@@ -877,6 +889,9 @@
String str = stringIter.next();
if(str == null) { break; }
if(ignoreString(str) || str.contentEquals(nfdString)) { continue; }
+ if (loop++ > kClosureLoopLimit) {
+ throw new ICUInputTooLongException("Too many closure");
+ }
ce32 = addIfDifferent(prefix, str, newCEs, newCEsLength, ce32);
}
} else {
@@ -891,6 +906,9 @@
String str = stringIter.next();
if(str == null) { break; }
if(ignoreString(str) || (samePrefix && str.contentEquals(nfdString))) { continue; }
+ if (loop++ > kClosureLoopLimit) {
+ throw new ICUInputTooLongException("Too many closure");
+ }
ce32 = addIfDifferent(prefix, str, newCEs, newCEsLength, ce32);
}
stringIter.reset();
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java b/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java
index ac47e51..c774af4 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java
@@ -3,8 +3,14 @@
// License & terms of use: http://www.unicode.org/copyright.html
package android.icu.impl.locale;
+import java.util.HashMap;
+import java.util.List;
import java.util.Objects;
+import android.icu.lang.UCharacter;
+import android.icu.lang.UProperty;
+import android.icu.lang.UScript;
+
/**
* @hide Only a subset of ICU is exposed in Android
*/
@@ -93,4 +99,146 @@
public int hashCode() {
return Objects.hash(language, script, region, flags);
}
+
+ // This method is added only to support encodeToIntForResource()
+ // It only support [a-z]{2,3} and will not work for other cases.
+ private int encodeLanguageToInt() {
+ assert language.length() >= 2;
+ assert language.length() <= 3;
+ assert language.charAt(0) >= 'a';
+ assert language.charAt(0) <= 'z';
+ assert language.charAt(1) >= 'a';
+ assert language.charAt(1) <= 'z';
+ assert language.length() == 2 || language.charAt(2) >= 'a';
+ assert language.length() == 2 || language.charAt(2) <= 'z';
+ return language.charAt(0) - 'a' + 1 +
+ 27 * (language.charAt(1) - 'a' + 1) +
+ ((language.length() == 2) ? 0 : 27 * 27 * (language.charAt(2) - 'a' + 1));
+ }
+ // This method is added only to support encodeToIntForResource()
+ // It only support [A-Z][a-z]{3} which defined in UScript and does not work for other cases.
+ private int encodeScriptToInt() {
+ int ret = UScript.getCodeFromName(script);
+ assert ret != UScript.INVALID_CODE;
+ return ret;
+ }
+ // This method is added only to support encodeToIntForResource()
+ // It only support [A-Z]{2} and the code in m49 but does not work for other cases.
+ private int encodeRegionToInt(List<String> m49) {
+ assert region.length() >= 2;
+ assert region.length() <= 3;
+ if (region.length() == 3) {
+ int index = m49.indexOf(region);
+ assert index >= 0;
+ if (index < 0) {
+ throw new IllegalStateException(
+ "Please add '" + region + "' to M49 in LocaleDistanceMapper.java");
+ }
+ return index;
+ }
+ assert region.charAt(0) >= 'A';
+ assert region.charAt(0) <= 'Z';
+ assert region.charAt(1) >= 'A';
+ assert region.charAt(1) <= 'Z';
+ // 'AA' => 1+27*1 = 28
+ // ...
+ // 'AZ' => 1+27*26 = 703
+ // 'BA' => 2+27*1 = 29
+ // ...
+ // 'IN' => 9+27*14 = 387
+ // 'ZZ' => 26+27*26 = 728
+ return (region.charAt(0) - 'A' + 1) + 27 * (region.charAt(1) - 'A' + 1);
+ }
+ // This is designed to only support encoding some LSR into resources but not for other cases.
+ public int encodeToIntForResource(List<String> m49) {
+ return (encodeLanguageToInt() + (27*27*27) * encodeRegionToInt(m49)) |
+ (encodeScriptToInt() << 24);
+ }
+
+ // BEGIN Android patch: Save ~1MB zygote heap. http://b/331291118
+ // ~7k LSR instances and ~21k strings are created from this path.
+ private static class CachedDecoder {
+ private static final String[] DECODED_ZERO =
+ new String[] {/*lang=*/ "", /*script=*/ "", /*region=*/ ""};
+ private static final String[] DECODED_ONE =
+ new String[] {/*lang=*/ "skip", /*script=*/ "script", /*region=*/ ""};
+
+ private final HashMap<Integer, String> langsCache;
+ private final HashMap<Integer, String> scriptsCache;
+ private final HashMap<Integer, String> regionsCache;
+
+ private final String[] m49;
+
+ CachedDecoder(String[] m49) {
+ int estLangCacheCapacity = 556; // ~= LocaleIDs._languages.length
+ langsCache = new HashMap<>(estLangCacheCapacity);
+ scriptsCache = new HashMap<>(UCharacter.getIntPropertyMaxValue(UProperty.SCRIPT));
+ int estRegionCacheCapacity = 253; // ~= LocaleIDs._countries.length
+ regionsCache = new HashMap<>(estRegionCacheCapacity);
+ this.m49 = m49;
+ }
+
+ /**
+ * @return a String[3] object where the first element is a language code, the second element
+ * is a script code, and the third element is a region code.
+ */
+ String[] decode(int encoded) {
+ if (encoded == 0) {
+ return DECODED_ZERO;
+ }
+ if (encoded == 1) {
+ return DECODED_ONE;
+ }
+
+ int encodedLang = encoded & 0x00ffffff;
+ encodedLang %= 27*27*27;
+ String lang = langsCache.computeIfAbsent(encodedLang, CachedDecoder::toLanguage);
+
+ int encodedScript = (encoded >> 24) & 0x000000ff;
+ String script = scriptsCache.computeIfAbsent(encodedScript, UScript::getShortName);
+
+ int encodedRegion = encoded & 0x00ffffff;
+ encodedRegion /= 27 * 27 * 27;
+ encodedRegion %= 27 * 27;
+
+ String region;
+ if (encodedRegion < 27) {
+ region = m49[encodedRegion];
+ } else {
+ region = regionsCache.computeIfAbsent(encodedRegion, CachedDecoder::toRegion);
+ }
+
+ return new String[] {lang, script, region};
+ }
+
+ private static String toLanguage(int encoded) {
+ StringBuilder res = new StringBuilder(3);
+ res.append((char)('a' + ((encoded % 27) - 1)));
+ res.append((char)('a' + (((encoded / 27 ) % 27) - 1)));
+ if (encoded / (27 * 27) != 0) {
+ res.append((char)('a' + ((encoded / (27 * 27)) - 1)));
+ }
+ return res.toString();
+ }
+
+ private static String toRegion(int encoded) {
+ StringBuilder res = new StringBuilder(3);
+ res.append((char)('A' + ((encoded % 27) - 1)));
+ res.append((char)('A' + (((encoded / 27) % 27) - 1)));
+ return res.toString();
+ }
+ }
+
+ public static LSR[] decodeInts(int[] nums, String[] m49) {
+ LSR[] lsrs = new LSR[nums.length];
+
+ CachedDecoder decoder = new CachedDecoder(m49);
+ for (int i = 0; i < nums.length; ++i) {
+ int encoded = nums[i];
+ String[] lsrStrings = decoder.decode(encoded);
+ lsrs[i] = new LSR(lsrStrings[0], lsrStrings[1], lsrStrings[2], LSR.IMPLICIT_LSR);
+ }
+ return lsrs;
+ }
+ // END Android patch: Save ~1MB zygote heap. http://b/331291118
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/LikelySubtags.java b/android_icu4j/src/main/java/android/icu/impl/locale/LikelySubtags.java
new file mode 100644
index 0000000..6c4b699
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/LikelySubtags.java
@@ -0,0 +1,595 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+package android.icu.impl.locale;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.TreeMap;
+
+import android.icu.impl.ICUData;
+import android.icu.impl.ICUResourceBundle;
+import android.icu.impl.UResource;
+import android.icu.util.BytesTrie;
+import android.icu.util.Region;
+import android.icu.util.ULocale;
+
+/**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+public final class LikelySubtags {
+ private static final String PSEUDO_ACCENTS_PREFIX = "'"; // -XA, -PSACCENT
+ private static final String PSEUDO_BIDI_PREFIX = "+"; // -XB, -PSBIDI
+ private static final String PSEUDO_CRACKED_PREFIX = ","; // -XC, -PSCRACK
+
+ public static final int SKIP_SCRIPT = 1;
+
+ private static final boolean DEBUG_OUTPUT = LSR.DEBUG_OUTPUT;
+
+ // VisibleForTesting
+ /**
+ * @hide Only a subset of ICU is exposed in Android
+ */
+ public static final class Data {
+ public final Map<String, String> languageAliases;
+ public final Map<String, String> regionAliases;
+ public final byte[] trie;
+ public final LSR[] lsrs;
+
+ public Data(Map<String, String> languageAliases, Map<String, String> regionAliases,
+ byte[] trie, LSR[] lsrs) {
+ this.languageAliases = languageAliases;
+ this.regionAliases = regionAliases;
+ this.trie = trie;
+ this.lsrs = lsrs;
+ }
+
+ private static UResource.Value getValue(UResource.Table table,
+ String key, UResource.Value value) {
+ if (!table.findValue(key, value)) {
+ throw new MissingResourceException(
+ "langInfo.res missing data", "", "likely/" + key);
+ }
+ return value;
+ }
+
+ // VisibleForTesting
+ public static Data load() throws MissingResourceException {
+ ICUResourceBundle langInfo = ICUResourceBundle.getBundleInstance(
+ ICUData.ICU_BASE_NAME, "langInfo",
+ ICUResourceBundle.ICU_DATA_CLASS_LOADER, ICUResourceBundle.OpenType.DIRECT);
+ UResource.Value value = langInfo.getValueWithFallback("likely");
+ UResource.Table likelyTable = value.getTable();
+
+ Map<String, String> languageAliases;
+ if (likelyTable.findValue("languageAliases", value)) {
+ String[] pairs = value.getStringArray();
+ languageAliases = new HashMap<>(pairs.length / 2);
+ for (int i = 0; i < pairs.length; i += 2) {
+ languageAliases.put(pairs[i], pairs[i + 1]);
+ }
+ } else {
+ languageAliases = Collections.emptyMap();
+ }
+
+ Map<String, String> regionAliases;
+ if (likelyTable.findValue("regionAliases", value)) {
+ String[] pairs = value.getStringArray();
+ regionAliases = new HashMap<>(pairs.length / 2);
+ for (int i = 0; i < pairs.length; i += 2) {
+ regionAliases.put(pairs[i], pairs[i + 1]);
+ }
+ } else {
+ regionAliases = Collections.emptyMap();
+ }
+
+ ByteBuffer buffer = getValue(likelyTable, "trie", value).getBinary();
+ byte[] trie = new byte[buffer.remaining()];
+ buffer.get(trie);
+
+ String[] m49 = getValue(likelyTable, "m49", value).getStringArray();
+ LSR[] lsrs = LSR.decodeInts(getValue(likelyTable, "lsrnum", value).getIntVector(), m49);
+ return new Data(languageAliases, regionAliases, trie, lsrs);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) { return true; }
+ if (other == null || !getClass().equals(other.getClass())) { return false; }
+ Data od = (Data)other;
+ return
+ languageAliases.equals(od.languageAliases) &&
+ regionAliases.equals(od.regionAliases) &&
+ Arrays.equals(trie, od.trie) &&
+ Arrays.equals(lsrs, od.lsrs);
+ }
+
+ @Override
+ public int hashCode() { // unused; silence ErrorProne
+ return 1;
+ }
+ }
+
+ // VisibleForTesting
+ public static final LikelySubtags INSTANCE = new LikelySubtags(Data.load());
+
+ private final Map<String, String> languageAliases;
+ private final Map<String, String> regionAliases;
+
+ // The trie maps each lang+script+region (encoded in ASCII) to an index into lsrs.
+ // There is also a trie value for each intermediate lang and lang+script.
+ // '*' is used instead of "und", "Zzzz"/"" and "ZZ"/"".
+ private final BytesTrie trie;
+ private final long trieUndState;
+ private final long trieUndZzzzState;
+ private final int defaultLsrIndex;
+ private final long[] trieFirstLetterStates = new long[26];
+ private final LSR[] lsrs;
+
+ private LikelySubtags(LikelySubtags.Data data) {
+ languageAliases = data.languageAliases;
+ regionAliases = data.regionAliases;
+ trie = new BytesTrie(data.trie, 0);
+ lsrs = data.lsrs;
+
+ // Cache the result of looking up language="und" encoded as "*", and "und-Zzzz" ("**").
+ BytesTrie.Result result = trie.next('*');
+ assert result.hasNext();
+ trieUndState = trie.getState64();
+ result = trie.next('*');
+ assert result.hasNext();
+ trieUndZzzzState = trie.getState64();
+ result = trie.next('*');
+ assert result.hasValue();
+ defaultLsrIndex = trie.getValue();
+ trie.reset();
+
+ for (char c = 'a'; c <= 'z'; ++c) {
+ result = trie.next(c);
+ if (result == BytesTrie.Result.NO_VALUE) {
+ trieFirstLetterStates[c - 'a'] = trie.getState64();
+ }
+ trie.reset();
+ }
+
+ if (DEBUG_OUTPUT) {
+ System.out.println("*** likely subtags");
+ for (Map.Entry<String, LSR> mapping : getTable().entrySet()) {
+ System.out.println(mapping);
+ }
+ }
+ }
+
+ /**
+ * Implementation of LocaleMatcher.canonicalize(ULocale).
+ */
+ public ULocale canonicalize(ULocale locale) {
+ String lang = locale.getLanguage();
+ String lang2 = languageAliases.get(lang);
+ String region = locale.getCountry();
+ String region2 = regionAliases.get(region);
+ if (lang2 != null || region2 != null) {
+ return new ULocale(
+ lang2 == null ? lang : lang2,
+ locale.getScript(),
+ region2 == null ? region : region2);
+ }
+ return locale;
+ }
+
+ private static String getCanonical(Map<String, String> aliases, String alias) {
+ String canonical = aliases.get(alias);
+ return canonical == null ? alias : canonical;
+ }
+
+ // VisibleForTesting
+ public LSR makeMaximizedLsrFrom(ULocale locale, boolean returnInputIfUnmatch) {
+ String name = locale.getName(); // Faster than .toLanguageTag().
+ if (name.startsWith("@x=")) {
+ String tag = locale.toLanguageTag();
+ assert tag.startsWith("und-x-");
+ // Private use language tag x-subtag-subtag... which CLDR changes to
+ // und-x-subtag-subtag...
+ return new LSR(tag, "", "", LSR.EXPLICIT_LSR);
+ }
+ LSR max = makeMaximizedLsr(locale.getLanguage(), locale.getScript(), locale.getCountry(),
+ locale.getVariant(), returnInputIfUnmatch);
+ if (max.language.isEmpty() && max.script.isEmpty() && max.region.isEmpty()) {
+ return new LSR(locale.getLanguage(), locale.getScript(), locale.getCountry(), LSR.EXPLICIT_LSR);
+ }
+ return max;
+ }
+
+ public LSR makeMaximizedLsrFrom(Locale locale) {
+ String tag = locale.toLanguageTag();
+ if (tag.startsWith("x-") || tag.startsWith("und-x-")) {
+ // Private use language tag x-subtag-subtag... which CLDR changes to
+ // und-x-subtag-subtag...
+ return new LSR(tag, "", "", LSR.EXPLICIT_LSR);
+ }
+ return makeMaximizedLsr(locale.getLanguage(), locale.getScript(), locale.getCountry(),
+ locale.getVariant(), false);
+ }
+
+ private LSR makeMaximizedLsr(String language, String script, String region, String variant, boolean returnInputIfUnmatch) {
+ // Handle pseudolocales like en-XA, ar-XB, fr-PSCRACK.
+ // They should match only themselves,
+ // not other locales with what looks like the same language and script subtags.
+ if (!returnInputIfUnmatch) {
+ if (region.length() == 2 && region.charAt(0) == 'X') {
+ switch (region.charAt(1)) {
+ case 'A':
+ return new LSR(PSEUDO_ACCENTS_PREFIX + language,
+ PSEUDO_ACCENTS_PREFIX + script, region, LSR.EXPLICIT_LSR);
+ case 'B':
+ return new LSR(PSEUDO_BIDI_PREFIX + language,
+ PSEUDO_BIDI_PREFIX + script, region, LSR.EXPLICIT_LSR);
+ case 'C':
+ return new LSR(PSEUDO_CRACKED_PREFIX + language,
+ PSEUDO_CRACKED_PREFIX + script, region, LSR.EXPLICIT_LSR);
+ default: // normal locale
+ break;
+ }
+ }
+
+ if (variant.startsWith("PS")) {
+ int lsrFlags = region.isEmpty() ?
+ LSR.EXPLICIT_LANGUAGE | LSR.EXPLICIT_SCRIPT : LSR.EXPLICIT_LSR;
+ switch (variant) {
+ case "PSACCENT":
+ return new LSR(PSEUDO_ACCENTS_PREFIX + language,
+ PSEUDO_ACCENTS_PREFIX + script,
+ region.isEmpty() ? "XA" : region, lsrFlags);
+ case "PSBIDI":
+ return new LSR(PSEUDO_BIDI_PREFIX + language,
+ PSEUDO_BIDI_PREFIX + script,
+ region.isEmpty() ? "XB" : region, lsrFlags);
+ case "PSCRACK":
+ return new LSR(PSEUDO_CRACKED_PREFIX + language,
+ PSEUDO_CRACKED_PREFIX + script,
+ region.isEmpty() ? "XC" : region, lsrFlags);
+ default: // normal locale
+ break;
+ }
+ }
+ }
+
+ language = getCanonical(languageAliases, language);
+ // (We have no script mappings.)
+ region = getCanonical(regionAliases, region);
+ return maximize(language, script, region, returnInputIfUnmatch);
+ }
+
+ /**
+ * Helper method to find out a region is a macroregion
+ */
+ private boolean isMacroregion(String region) {
+ Region.RegionType type = Region.getInstance(region).getType();
+ return type == Region.RegionType.WORLD ||
+ type == Region.RegionType.CONTINENT ||
+ type == Region.RegionType.SUBCONTINENT ;
+ }
+
+ /**
+ * Raw access to addLikelySubtags. Input must be in canonical format, eg "en", not "eng" or "EN".
+ */
+ private LSR maximize(String language, String script, String region, boolean returnInputIfUnmatch) {
+ if (language.equals("und")) {
+ language = "";
+ }
+ if (script.equals("Zzzz")) {
+ script = "";
+ }
+ if (region.equals("ZZ")) {
+ region = "";
+ }
+ if (!script.isEmpty() && !region.isEmpty() && !language.isEmpty()) {
+ return new LSR(language, script, region, LSR.EXPLICIT_LSR); // already maximized
+ }
+
+ boolean retainLanguage = false;
+ boolean retainScript = false;
+ boolean retainRegion = false;
+ BytesTrie iter = new BytesTrie(trie);
+ long state;
+ int value;
+ // Small optimization: Array lookup for first language letter.
+ int c0;
+ if (language.length() >= 2 && 0 <= (c0 = language.charAt(0) - 'a') && c0 <= 25 &&
+ (state = trieFirstLetterStates[c0]) != 0) {
+ value = trieNext(iter.resetToState64(state), language, 1);
+ } else {
+ value = trieNext(iter, language, 0);
+ }
+ boolean matchLanguage = (value >= 0);
+ boolean matchScript = false;
+ if (value >= 0) {
+ retainLanguage = ! language.isEmpty();
+ state = iter.getState64();
+ } else {
+ retainLanguage = true;
+ iter.resetToState64(trieUndState); // "und" ("*")
+ state = 0;
+ }
+
+ if (value >= 0 && !script.isEmpty()) {
+ matchScript = true;
+ }
+ if (value > 0) {
+ // Intermediate or final value from just language.
+ if (value == SKIP_SCRIPT) {
+ value = 0;
+ }
+ retainScript = ! script.isEmpty();
+ } else {
+ value = trieNext(iter, script, 0);
+ if (value >= 0) {
+ retainScript = ! script.isEmpty();
+ state = iter.getState64();
+ } else {
+ retainScript = true;
+ if (state == 0) {
+ iter.resetToState64(trieUndZzzzState); // "und-Zzzz" ("**")
+ } else {
+ iter.resetToState64(state);
+ value = trieNext(iter, "", 0);
+ assert value >= 0;
+ state = iter.getState64();
+ }
+ }
+ }
+
+ boolean matchRegion = false;
+ if (value > 0) {
+ // Final value from just language or language+script.
+ retainRegion = ! region.isEmpty();
+ } else {
+ value = trieNext(iter, region, 0);
+ if (value >= 0) {
+ if (!region.isEmpty() && !isMacroregion(region)) {
+ retainRegion = true;
+ matchRegion = true;
+ }
+ } else {
+ retainRegion = true;
+ if (state == 0) {
+ value = defaultLsrIndex;
+ } else {
+ iter.resetToState64(state);
+ value = trieNext(iter, "", 0);
+ assert value > 0;
+ }
+ }
+ }
+ LSR result = lsrs[value];
+
+ if (returnInputIfUnmatch &&
+ (!(matchLanguage || matchScript || (matchRegion && language.isEmpty())))) {
+ return new LSR("", "", "", LSR.EXPLICIT_LSR); // no matching.
+ }
+ if (language.isEmpty()) {
+ language = "und";
+ }
+
+ if (! (retainLanguage || retainScript || retainRegion)) {
+ assert result.flags == LSR.IMPLICIT_LSR;
+ return result;
+ }
+ if (!retainLanguage) {
+ language = result.language;
+ }
+ if (!retainScript) {
+ script = result.script;
+ }
+ if (!retainRegion) {
+ region = result.region;
+ }
+ int retainMask = (retainLanguage ? 4 : 0) + (retainScript ? 2 : 0) + (retainRegion ? 1 : 0);
+ // retainOldMask flags = LSR explicit-subtag flags
+ return new LSR(language, script, region, retainMask);
+ }
+
+ /**
+ * Tests whether lsr is "more likely" than other.
+ * For example, fr-Latn-FR is more likely than fr-Latn-CH because
+ * FR is the default region for fr-Latn.
+ *
+ * <p>The likelyInfo caches lookup information between calls.
+ * The return value is an updated likelyInfo value,
+ * with bit 0 set if lsr is "more likely".
+ * The initial value of likelyInfo must be negative.
+ */
+ int compareLikely(LSR lsr, LSR other, int likelyInfo) {
+ // If likelyInfo >= 0:
+ // likelyInfo bit 1 is set if the previous comparison with lsr
+ // was for equal language and script.
+ // Otherwise the scripts differed.
+ if (!lsr.language.equals(other.language)) {
+ return 0xfffffffc; // negative, lsr not better than other
+ }
+ if (!lsr.script.equals(other.script)) {
+ int index;
+ if (likelyInfo >= 0 && (likelyInfo & 2) == 0) {
+ index = likelyInfo >> 2;
+ } else {
+ index = getLikelyIndex(lsr.language, "");
+ likelyInfo = index << 2;
+ }
+ LSR likely = lsrs[index];
+ if (lsr.script.equals(likely.script)) {
+ return likelyInfo | 1;
+ } else {
+ return likelyInfo & ~1;
+ }
+ }
+ if (!lsr.region.equals(other.region)) {
+ int index;
+ if (likelyInfo >= 0 && (likelyInfo & 2) != 0) {
+ index = likelyInfo >> 2;
+ } else {
+ index = getLikelyIndex(lsr.language, lsr.region);
+ likelyInfo = (index << 2) | 2;
+ }
+ LSR likely = lsrs[index];
+ if (lsr.region.equals(likely.region)) {
+ return likelyInfo | 1;
+ } else {
+ return likelyInfo & ~1;
+ }
+ }
+ return likelyInfo & ~1; // lsr not better than other
+ }
+
+ // Subset of maximize().
+ private int getLikelyIndex(String language, String script) {
+ if (language.equals("und")) {
+ language = "";
+ }
+ if (script.equals("Zzzz")) {
+ script = "";
+ }
+
+ BytesTrie iter = new BytesTrie(trie);
+ long state;
+ int value;
+ // Small optimization: Array lookup for first language letter.
+ int c0;
+ if (language.length() >= 2 && 0 <= (c0 = language.charAt(0) - 'a') && c0 <= 25 &&
+ (state = trieFirstLetterStates[c0]) != 0) {
+ value = trieNext(iter.resetToState64(state), language, 1);
+ } else {
+ value = trieNext(iter, language, 0);
+ }
+ if (value >= 0) {
+ state = iter.getState64();
+ } else {
+ iter.resetToState64(trieUndState); // "und" ("*")
+ state = 0;
+ }
+
+ if (value > 0) {
+ // Intermediate or final value from just language.
+ if (value == SKIP_SCRIPT) {
+ value = 0;
+ }
+ } else {
+ value = trieNext(iter, script, 0);
+ if (value >= 0) {
+ state = iter.getState64();
+ } else {
+ if (state == 0) {
+ iter.resetToState64(trieUndZzzzState); // "und-Zzzz" ("**")
+ } else {
+ iter.resetToState64(state);
+ value = trieNext(iter, "", 0);
+ assert value >= 0;
+ state = iter.getState64();
+ }
+ }
+ }
+
+ if (value > 0) {
+ // Final value from just language or language+script.
+ } else {
+ value = trieNext(iter, "", 0);
+ assert value > 0;
+ }
+ return value;
+ }
+
+ private static final int trieNext(BytesTrie iter, String s, int i) {
+ BytesTrie.Result result;
+ if (s.isEmpty()) {
+ result = iter.next('*');
+ } else {
+ int end = s.length() - 1;
+ for (;; ++i) {
+ int c = s.charAt(i);
+ if (i < end) {
+ if (!iter.next(c).hasNext()) {
+ return -1;
+ }
+ } else {
+ // last character of this subtag
+ result = iter.next(c | 0x80);
+ break;
+ }
+ }
+ }
+ switch (result) {
+ case NO_MATCH: return -1;
+ case NO_VALUE: return 0;
+ case INTERMEDIATE_VALUE:
+ assert iter.getValue() == SKIP_SCRIPT;
+ return SKIP_SCRIPT;
+ case FINAL_VALUE: return iter.getValue();
+ default: return -1;
+ }
+ }
+
+ public LSR minimizeSubtags(String languageIn, String scriptIn, String regionIn,
+ ULocale.Minimize fieldToFavor) {
+ LSR max = maximize(languageIn, scriptIn, regionIn, true);
+ if (max.language.isEmpty() && max.region.isEmpty() && max.script.isEmpty()) {
+ // Cannot match, return as is
+ return new LSR(languageIn, scriptIn, regionIn, LSR.EXPLICIT_LSR);
+ }
+ LSR test = maximize(max.language, "", "", true);
+ if (test.isEquivalentTo(max)) {
+ return new LSR(max.language, "", "", LSR.DONT_CARE_FLAGS);
+ }
+ if (ULocale.Minimize.FAVOR_REGION == fieldToFavor) {
+ test = maximize(max.language, "", max.region, true);
+ if (test.isEquivalentTo(max)) {
+ return new LSR(max.language, "", max.region, LSR.DONT_CARE_FLAGS);
+ }
+ test = maximize(max.language, max.script, "", true);
+ if (test.isEquivalentTo(max)) {
+ return new LSR(max.language, max.script, "", LSR.DONT_CARE_FLAGS);
+ }
+ } else {
+ test = maximize(max.language, max.script, "", true);
+ if (test.isEquivalentTo(max)) {
+ return new LSR(max.language, max.script, "", LSR.DONT_CARE_FLAGS);
+ }
+ test = maximize(max.language, "", max.region, true);
+ if (test.isEquivalentTo(max)) {
+ return new LSR(max.language, "", max.region, LSR.DONT_CARE_FLAGS);
+ }
+ }
+ return new LSR(max.language, max.script, max.region, LSR.DONT_CARE_FLAGS);
+ }
+
+ private Map<String, LSR> getTable() {
+ Map<String, LSR> map = new TreeMap<>();
+ StringBuilder sb = new StringBuilder();
+ for (BytesTrie.Entry entry : trie) {
+ sb.setLength(0);
+ int length = entry.bytesLength();
+ for (int i = 0; i < length;) {
+ byte b = entry.byteAt(i++);
+ if (b == '*') {
+ sb.append("*-");
+ } else if (b >= 0) {
+ sb.append((char) b);
+ } else { // end of subtag
+ sb.append((char) (b & 0x7f)).append('-');
+ }
+ }
+ assert sb.length() > 0 && sb.charAt(sb.length() - 1) == '-';
+ sb.setLength(sb.length() - 1);
+ map.put(sb.toString(), lsrs[entry.value]);
+ }
+ return map;
+ }
+
+ @Override
+ public String toString() {
+ return getTable().toString();
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java b/android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java
index de6fe96..4416108 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java
@@ -156,14 +156,12 @@
String[] partitions = getValue(matchTable, "partitions", value).getStringArray();
Set<LSR> paradigmLSRs;
- if (matchTable.findValue("paradigms", value)) {
- String[] paradigms = value.getStringArray();
+ if (matchTable.findValue("paradigmnum", value)) {
+ String[] m49 = getValue(langInfo.getValueWithFallback("likely").getTable(),
+ "m49", value).getStringArray();
+ LSR[] paradigms = LSR.decodeInts(getValue(matchTable, "paradigmnum", value).getIntVector(), m49);
// LinkedHashSet for stable order; otherwise a unit test is flaky.
- paradigmLSRs = new LinkedHashSet<>(paradigms.length / 3);
- for (int i = 0; i < paradigms.length; i += 3) {
- paradigmLSRs.add(new LSR(paradigms[i], paradigms[i + 1], paradigms[i + 2],
- LSR.DONT_CARE_FLAGS));
- }
+ paradigmLSRs = new LinkedHashSet<LSR>(Arrays.asList(paradigms));
} else {
paradigmLSRs = Collections.emptySet();
}
@@ -232,8 +230,8 @@
// VisibleForTesting
public int testOnlyDistance(ULocale desired, ULocale supported,
int threshold, FavorSubtag favorSubtag) {
- LSR supportedLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(supported);
- LSR desiredLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(desired);
+ LSR supportedLSR = LikelySubtags.INSTANCE.makeMaximizedLsrFrom(supported, false);
+ LSR desiredLSR = LikelySubtags.INSTANCE.makeMaximizedLsrFrom(desired, false);
int indexAndDistance = getBestIndexAndDistance(desiredLSR, new LSR[] { supportedLSR }, 1,
shiftDistance(threshold), favorSubtag, LocaleMatcher.Direction.WITH_ONE_WAY);
return getDistanceFloor(indexAndDistance);
@@ -257,7 +255,7 @@
long desLangState = desLangDistance >= 0 && supportedLSRsLength > 1 ? iter.getState64() : 0;
// Index of the supported LSR with the lowest distance.
int bestIndex = -1;
- // Cached lookup info from XLikelySubtags.compareLikely().
+ // Cached lookup info from LikelySubtags.compareLikely().
int bestLikelyInfo = -1;
for (int slIndex = 0; slIndex < supportedLSRsLength; ++slIndex) {
LSR supported = supportedLSRs[slIndex];
@@ -376,7 +374,7 @@
if (direction != LocaleMatcher.Direction.ONLY_TWO_WAY ||
// Is there also a match when we swap desired/supported?
isMatch(supported, desired, shiftedThreshold, favorSubtag)) {
- bestLikelyInfo = XLikelySubtags.INSTANCE.compareLikely(
+ bestLikelyInfo = LikelySubtags.INSTANCE.compareLikely(
supported, supportedLSRs[bestIndex], bestLikelyInfo);
if ((bestLikelyInfo & 1) != 0) {
// This supported locale matches as well as the previous best match,
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/LocaleExtensions.java b/android_icu4j/src/main/java/android/icu/impl/locale/LocaleExtensions.java
index 0e1ed89..94242c0 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/LocaleExtensions.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/LocaleExtensions.java
@@ -44,12 +44,12 @@
CALENDAR_JAPANESE = new LocaleExtensions();
CALENDAR_JAPANESE._id = "u-ca-japanese";
CALENDAR_JAPANESE._map = new TreeMap<Character, Extension>();
- CALENDAR_JAPANESE._map.put(Character.valueOf(UnicodeLocaleExtension.SINGLETON), UnicodeLocaleExtension.CA_JAPANESE);
+ CALENDAR_JAPANESE._map.put(UnicodeLocaleExtension.SINGLETON, UnicodeLocaleExtension.CA_JAPANESE);
NUMBER_THAI = new LocaleExtensions();
NUMBER_THAI._id = "u-nu-thai";
NUMBER_THAI._map = new TreeMap<Character, Extension>();
- NUMBER_THAI._map.put(Character.valueOf(UnicodeLocaleExtension.SINGLETON), UnicodeLocaleExtension.NU_THAI);
+ NUMBER_THAI._map.put(UnicodeLocaleExtension.SINGLETON, UnicodeLocaleExtension.NU_THAI);
}
private LocaleExtensions() {
@@ -71,7 +71,7 @@
}
// Build extension map
- _map = new TreeMap<Character, Extension>();
+ _map = new TreeMap<>();
if (hasExtension) {
for (Entry<CaseInsensitiveChar, String> ext : extensions.entrySet()) {
char key = AsciiUtil.toLower(ext.getKey().value());
@@ -86,7 +86,7 @@
}
Extension e = new Extension(key, AsciiUtil.toLowerString(value));
- _map.put(Character.valueOf(key), e);
+ _map.put(key, e);
}
}
@@ -111,7 +111,7 @@
}
UnicodeLocaleExtension ule = new UnicodeLocaleExtension(uaset, ukmap);
- _map.put(Character.valueOf(UnicodeLocaleExtension.SINGLETON), ule);
+ _map.put(UnicodeLocaleExtension.SINGLETON, ule);
}
if (_map.size() == 0) {
@@ -128,11 +128,11 @@
}
public Extension getExtension(Character key) {
- return _map.get(Character.valueOf(AsciiUtil.toLower(key.charValue())));
+ return _map.get(AsciiUtil.toLower(key.charValue()));
}
public String getExtensionValue(Character key) {
- Extension ext = _map.get(Character.valueOf(AsciiUtil.toLower(key.charValue())));
+ Extension ext = _map.get(AsciiUtil.toLower(key.charValue()));
if (ext == null) {
return null;
}
@@ -140,7 +140,7 @@
}
public Set<String> getUnicodeLocaleAttributes() {
- Extension ext = _map.get(Character.valueOf(UnicodeLocaleExtension.SINGLETON));
+ Extension ext = _map.get(UnicodeLocaleExtension.SINGLETON);
if (ext == null) {
return Collections.emptySet();
}
@@ -149,7 +149,7 @@
}
public Set<String> getUnicodeLocaleKeys() {
- Extension ext = _map.get(Character.valueOf(UnicodeLocaleExtension.SINGLETON));
+ Extension ext = _map.get(UnicodeLocaleExtension.SINGLETON);
if (ext == null) {
return Collections.emptySet();
}
@@ -158,7 +158,7 @@
}
public String getUnicodeLocaleType(String unicodeLocaleKey) {
- Extension ext = _map.get(Character.valueOf(UnicodeLocaleExtension.SINGLETON));
+ Extension ext = _map.get(UnicodeLocaleExtension.SINGLETON);
if (ext == null) {
return null;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java b/android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java
deleted file mode 100644
index f8aea60..0000000
--- a/android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java
+++ /dev/null
@@ -1,590 +0,0 @@
-/* GENERATED SOURCE. DO NOT MODIFY. */
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
-package android.icu.impl.locale;
-
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.MissingResourceException;
-import java.util.TreeMap;
-
-import android.icu.impl.ICUData;
-import android.icu.impl.ICUResourceBundle;
-import android.icu.impl.UResource;
-import android.icu.util.BytesTrie;
-import android.icu.util.ULocale;
-
-/**
- * @hide Only a subset of ICU is exposed in Android
- */
-public final class XLikelySubtags {
- private static final String PSEUDO_ACCENTS_PREFIX = "'"; // -XA, -PSACCENT
- private static final String PSEUDO_BIDI_PREFIX = "+"; // -XB, -PSBIDI
- private static final String PSEUDO_CRACKED_PREFIX = ","; // -XC, -PSCRACK
-
- public static final int SKIP_SCRIPT = 1;
-
- private static final boolean DEBUG_OUTPUT = LSR.DEBUG_OUTPUT;
-
- // VisibleForTesting
- /**
- * @hide Only a subset of ICU is exposed in Android
- */
- public static final class Data {
- public final Map<String, String> languageAliases;
- public final Map<String, String> regionAliases;
- public final byte[] trie;
- public final LSR[] lsrs;
-
- public Data(Map<String, String> languageAliases, Map<String, String> regionAliases,
- byte[] trie, LSR[] lsrs) {
- this.languageAliases = languageAliases;
- this.regionAliases = regionAliases;
- this.trie = trie;
- this.lsrs = lsrs;
- }
-
- private static UResource.Value getValue(UResource.Table table,
- String key, UResource.Value value) {
- if (!table.findValue(key, value)) {
- throw new MissingResourceException(
- "langInfo.res missing data", "", "likely/" + key);
- }
- return value;
- }
-
- // VisibleForTesting
- public static Data load() throws MissingResourceException {
- ICUResourceBundle langInfo = ICUResourceBundle.getBundleInstance(
- ICUData.ICU_BASE_NAME, "langInfo",
- ICUResourceBundle.ICU_DATA_CLASS_LOADER, ICUResourceBundle.OpenType.DIRECT);
- UResource.Value value = langInfo.getValueWithFallback("likely");
- UResource.Table likelyTable = value.getTable();
-
- Map<String, String> languageAliases;
- if (likelyTable.findValue("languageAliases", value)) {
- String[] pairs = value.getStringArray();
- languageAliases = new HashMap<>(pairs.length / 2);
- for (int i = 0; i < pairs.length; i += 2) {
- languageAliases.put(pairs[i], pairs[i + 1]);
- }
- } else {
- languageAliases = Collections.emptyMap();
- }
-
- Map<String, String> regionAliases;
- if (likelyTable.findValue("regionAliases", value)) {
- String[] pairs = value.getStringArray();
- regionAliases = new HashMap<>(pairs.length / 2);
- for (int i = 0; i < pairs.length; i += 2) {
- regionAliases.put(pairs[i], pairs[i + 1]);
- }
- } else {
- regionAliases = Collections.emptyMap();
- }
-
- ByteBuffer buffer = getValue(likelyTable, "trie", value).getBinary();
- byte[] trie = new byte[buffer.remaining()];
- buffer.get(trie);
-
- String[] lsrSubtags = getValue(likelyTable, "lsrs", value).getStringArray();
- LSR[] lsrs = new LSR[lsrSubtags.length / 3];
- for (int i = 0, j = 0; i < lsrSubtags.length; i += 3, ++j) {
- lsrs[j] = new LSR(lsrSubtags[i], lsrSubtags[i + 1], lsrSubtags[i + 2],
- LSR.IMPLICIT_LSR);
- }
-
- return new Data(languageAliases, regionAliases, trie, lsrs);
- }
-
- @Override
- public boolean equals(Object other) {
- if (this == other) { return true; }
- if (other == null || !getClass().equals(other.getClass())) { return false; }
- Data od = (Data)other;
- return
- languageAliases.equals(od.languageAliases) &&
- regionAliases.equals(od.regionAliases) &&
- Arrays.equals(trie, od.trie) &&
- Arrays.equals(lsrs, od.lsrs);
- }
-
- @Override
- public int hashCode() { // unused; silence ErrorProne
- return 1;
- }
- }
-
- // VisibleForTesting
- public static final XLikelySubtags INSTANCE = new XLikelySubtags(Data.load());
-
- private final Map<String, String> languageAliases;
- private final Map<String, String> regionAliases;
-
- // The trie maps each lang+script+region (encoded in ASCII) to an index into lsrs.
- // There is also a trie value for each intermediate lang and lang+script.
- // '*' is used instead of "und", "Zzzz"/"" and "ZZ"/"".
- private final BytesTrie trie;
- private final long trieUndState;
- private final long trieUndZzzzState;
- private final int defaultLsrIndex;
- private final long[] trieFirstLetterStates = new long[26];
- private final LSR[] lsrs;
-
- private XLikelySubtags(XLikelySubtags.Data data) {
- languageAliases = data.languageAliases;
- regionAliases = data.regionAliases;
- trie = new BytesTrie(data.trie, 0);
- lsrs = data.lsrs;
-
- // Cache the result of looking up language="und" encoded as "*", and "und-Zzzz" ("**").
- BytesTrie.Result result = trie.next('*');
- assert result.hasNext();
- trieUndState = trie.getState64();
- result = trie.next('*');
- assert result.hasNext();
- trieUndZzzzState = trie.getState64();
- result = trie.next('*');
- assert result.hasValue();
- defaultLsrIndex = trie.getValue();
- trie.reset();
-
- for (char c = 'a'; c <= 'z'; ++c) {
- result = trie.next(c);
- if (result == BytesTrie.Result.NO_VALUE) {
- trieFirstLetterStates[c - 'a'] = trie.getState64();
- }
- trie.reset();
- }
-
- if (DEBUG_OUTPUT) {
- System.out.println("*** likely subtags");
- for (Map.Entry<String, LSR> mapping : getTable().entrySet()) {
- System.out.println(mapping);
- }
- }
- }
-
- /**
- * Implementation of LocaleMatcher.canonicalize(ULocale).
- */
- public ULocale canonicalize(ULocale locale) {
- String lang = locale.getLanguage();
- String lang2 = languageAliases.get(lang);
- String region = locale.getCountry();
- String region2 = regionAliases.get(region);
- if (lang2 != null || region2 != null) {
- return new ULocale(
- lang2 == null ? lang : lang2,
- locale.getScript(),
- region2 == null ? region : region2);
- }
- return locale;
- }
-
- private static String getCanonical(Map<String, String> aliases, String alias) {
- String canonical = aliases.get(alias);
- return canonical == null ? alias : canonical;
- }
-
- // VisibleForTesting
- public LSR makeMaximizedLsrFrom(ULocale locale) {
- String name = locale.getName(); // Faster than .toLanguageTag().
- if (name.startsWith("@x=")) {
- String tag = locale.toLanguageTag();
- assert tag.startsWith("und-x-");
- // Private use language tag x-subtag-subtag... which CLDR changes to
- // und-x-subtag-subtag...
- return new LSR(tag, "", "", LSR.EXPLICIT_LSR);
- }
- return makeMaximizedLsr(locale.getLanguage(), locale.getScript(), locale.getCountry(),
- locale.getVariant());
- }
-
- public LSR makeMaximizedLsrFrom(Locale locale) {
- String tag = locale.toLanguageTag();
- if (tag.startsWith("x-") || tag.startsWith("und-x-")) {
- // Private use language tag x-subtag-subtag... which CLDR changes to
- // und-x-subtag-subtag...
- return new LSR(tag, "", "", LSR.EXPLICIT_LSR);
- }
- return makeMaximizedLsr(locale.getLanguage(), locale.getScript(), locale.getCountry(),
- locale.getVariant());
- }
-
- private LSR makeMaximizedLsr(String language, String script, String region, String variant) {
- // Handle pseudolocales like en-XA, ar-XB, fr-PSCRACK.
- // They should match only themselves,
- // not other locales with what looks like the same language and script subtags.
- if (region.length() == 2 && region.charAt(0) == 'X') {
- switch (region.charAt(1)) {
- case 'A':
- return new LSR(PSEUDO_ACCENTS_PREFIX + language,
- PSEUDO_ACCENTS_PREFIX + script, region, LSR.EXPLICIT_LSR);
- case 'B':
- return new LSR(PSEUDO_BIDI_PREFIX + language,
- PSEUDO_BIDI_PREFIX + script, region, LSR.EXPLICIT_LSR);
- case 'C':
- return new LSR(PSEUDO_CRACKED_PREFIX + language,
- PSEUDO_CRACKED_PREFIX + script, region, LSR.EXPLICIT_LSR);
- default: // normal locale
- break;
- }
- }
-
- if (variant.startsWith("PS")) {
- int lsrFlags = region.isEmpty() ?
- LSR.EXPLICIT_LANGUAGE | LSR.EXPLICIT_SCRIPT : LSR.EXPLICIT_LSR;
- switch (variant) {
- case "PSACCENT":
- return new LSR(PSEUDO_ACCENTS_PREFIX + language,
- PSEUDO_ACCENTS_PREFIX + script,
- region.isEmpty() ? "XA" : region, lsrFlags);
- case "PSBIDI":
- return new LSR(PSEUDO_BIDI_PREFIX + language,
- PSEUDO_BIDI_PREFIX + script,
- region.isEmpty() ? "XB" : region, lsrFlags);
- case "PSCRACK":
- return new LSR(PSEUDO_CRACKED_PREFIX + language,
- PSEUDO_CRACKED_PREFIX + script,
- region.isEmpty() ? "XC" : region, lsrFlags);
- default: // normal locale
- break;
- }
- }
-
- language = getCanonical(languageAliases, language);
- // (We have no script mappings.)
- region = getCanonical(regionAliases, region);
- return maximize(language, script, region);
- }
-
- /**
- * Raw access to addLikelySubtags. Input must be in canonical format, eg "en", not "eng" or "EN".
- */
- private LSR maximize(String language, String script, String region) {
- if (language.equals("und")) {
- language = "";
- }
- if (script.equals("Zzzz")) {
- script = "";
- }
- if (region.equals("ZZ")) {
- region = "";
- }
- if (!script.isEmpty() && !region.isEmpty() && !language.isEmpty()) {
- return new LSR(language, script, region, LSR.EXPLICIT_LSR); // already maximized
- }
-
- int retainOldMask = 0;
- BytesTrie iter = new BytesTrie(trie);
- long state;
- int value;
- // Small optimization: Array lookup for first language letter.
- int c0;
- if (language.length() >= 2 && 0 <= (c0 = language.charAt(0) - 'a') && c0 <= 25 &&
- (state = trieFirstLetterStates[c0]) != 0) {
- value = trieNext(iter.resetToState64(state), language, 1);
- } else {
- value = trieNext(iter, language, 0);
- }
- if (value >= 0) {
- if (!language.isEmpty()) {
- retainOldMask |= 4;
- }
- state = iter.getState64();
- } else {
- retainOldMask |= 4;
- iter.resetToState64(trieUndState); // "und" ("*")
- state = 0;
- }
-
- if (value > 0) {
- // Intermediate or final value from just language.
- if (value == SKIP_SCRIPT) {
- value = 0;
- }
- if (!script.isEmpty()) {
- retainOldMask |= 2;
- }
- } else {
- value = trieNext(iter, script, 0);
- if (value >= 0) {
- if (!script.isEmpty()) {
- retainOldMask |= 2;
- }
- state = iter.getState64();
- } else {
- retainOldMask |= 2;
- if (state == 0) {
- iter.resetToState64(trieUndZzzzState); // "und-Zzzz" ("**")
- } else {
- iter.resetToState64(state);
- value = trieNext(iter, "", 0);
- assert value >= 0;
- state = iter.getState64();
- }
- }
- }
-
- if (value > 0) {
- // Final value from just language or language+script.
- if (!region.isEmpty()) {
- retainOldMask |= 1;
- }
- } else {
- value = trieNext(iter, region, 0);
- if (value >= 0) {
- if (!region.isEmpty()) {
- retainOldMask |= 1;
- }
- } else {
- retainOldMask |= 1;
- if (state == 0) {
- value = defaultLsrIndex;
- } else {
- iter.resetToState64(state);
- value = trieNext(iter, "", 0);
- assert value > 0;
- }
- }
- }
- LSR result = lsrs[value];
-
- if (language.isEmpty()) {
- language = "und";
- }
-
- if (retainOldMask == 0) {
- assert result.flags == LSR.IMPLICIT_LSR;
- return result;
- }
- if ((retainOldMask & 4) == 0) {
- language = result.language;
- }
- if ((retainOldMask & 2) == 0) {
- script = result.script;
- }
- if ((retainOldMask & 1) == 0) {
- region = result.region;
- }
- // retainOldMask flags = LSR explicit-subtag flags
- return new LSR(language, script, region, retainOldMask);
- }
-
- /**
- * Tests whether lsr is "more likely" than other.
- * For example, fr-Latn-FR is more likely than fr-Latn-CH because
- * FR is the default region for fr-Latn.
- *
- * <p>The likelyInfo caches lookup information between calls.
- * The return value is an updated likelyInfo value,
- * with bit 0 set if lsr is "more likely".
- * The initial value of likelyInfo must be negative.
- */
- int compareLikely(LSR lsr, LSR other, int likelyInfo) {
- // If likelyInfo >= 0:
- // likelyInfo bit 1 is set if the previous comparison with lsr
- // was for equal language and script.
- // Otherwise the scripts differed.
- if (!lsr.language.equals(other.language)) {
- return 0xfffffffc; // negative, lsr not better than other
- }
- if (!lsr.script.equals(other.script)) {
- int index;
- if (likelyInfo >= 0 && (likelyInfo & 2) == 0) {
- index = likelyInfo >> 2;
- } else {
- index = getLikelyIndex(lsr.language, "");
- likelyInfo = index << 2;
- }
- LSR likely = lsrs[index];
- if (lsr.script.equals(likely.script)) {
- return likelyInfo | 1;
- } else {
- return likelyInfo & ~1;
- }
- }
- if (!lsr.region.equals(other.region)) {
- int index;
- if (likelyInfo >= 0 && (likelyInfo & 2) != 0) {
- index = likelyInfo >> 2;
- } else {
- index = getLikelyIndex(lsr.language, lsr.region);
- likelyInfo = (index << 2) | 2;
- }
- LSR likely = lsrs[index];
- if (lsr.region.equals(likely.region)) {
- return likelyInfo | 1;
- } else {
- return likelyInfo & ~1;
- }
- }
- return likelyInfo & ~1; // lsr not better than other
- }
-
- // Subset of maximize().
- private int getLikelyIndex(String language, String script) {
- if (language.equals("und")) {
- language = "";
- }
- if (script.equals("Zzzz")) {
- script = "";
- }
-
- BytesTrie iter = new BytesTrie(trie);
- long state;
- int value;
- // Small optimization: Array lookup for first language letter.
- int c0;
- if (language.length() >= 2 && 0 <= (c0 = language.charAt(0) - 'a') && c0 <= 25 &&
- (state = trieFirstLetterStates[c0]) != 0) {
- value = trieNext(iter.resetToState64(state), language, 1);
- } else {
- value = trieNext(iter, language, 0);
- }
- if (value >= 0) {
- state = iter.getState64();
- } else {
- iter.resetToState64(trieUndState); // "und" ("*")
- state = 0;
- }
-
- if (value > 0) {
- // Intermediate or final value from just language.
- if (value == SKIP_SCRIPT) {
- value = 0;
- }
- } else {
- value = trieNext(iter, script, 0);
- if (value >= 0) {
- state = iter.getState64();
- } else {
- if (state == 0) {
- iter.resetToState64(trieUndZzzzState); // "und-Zzzz" ("**")
- } else {
- iter.resetToState64(state);
- value = trieNext(iter, "", 0);
- assert value >= 0;
- state = iter.getState64();
- }
- }
- }
-
- if (value > 0) {
- // Final value from just language or language+script.
- } else {
- value = trieNext(iter, "", 0);
- assert value > 0;
- }
- return value;
- }
-
- private static final int trieNext(BytesTrie iter, String s, int i) {
- BytesTrie.Result result;
- if (s.isEmpty()) {
- result = iter.next('*');
- } else {
- int end = s.length() - 1;
- for (;; ++i) {
- int c = s.charAt(i);
- if (i < end) {
- if (!iter.next(c).hasNext()) {
- return -1;
- }
- } else {
- // last character of this subtag
- result = iter.next(c | 0x80);
- break;
- }
- }
- }
- switch (result) {
- case NO_MATCH: return -1;
- case NO_VALUE: return 0;
- case INTERMEDIATE_VALUE:
- assert iter.getValue() == SKIP_SCRIPT;
- return SKIP_SCRIPT;
- case FINAL_VALUE: return iter.getValue();
- default: return -1;
- }
- }
-
- LSR minimizeSubtags(String languageIn, String scriptIn, String regionIn,
- ULocale.Minimize fieldToFavor) {
- LSR result = maximize(languageIn, scriptIn, regionIn);
-
- // We could try just a series of checks, like:
- // LSR result2 = addLikelySubtags(languageIn, "", "");
- // if result.equals(result2) return result2;
- // However, we can optimize 2 of the cases:
- // (languageIn, "", "")
- // (languageIn, "", regionIn)
-
- // value00 = lookup(result.language, "", "")
- BytesTrie iter = new BytesTrie(trie);
- int value = trieNext(iter, result.language, 0);
- assert value >= 0;
- if (value == 0) {
- value = trieNext(iter, "", 0);
- assert value >= 0;
- if (value == 0) {
- value = trieNext(iter, "", 0);
- }
- }
- assert value > 0;
- LSR value00 = lsrs[value];
- boolean favorRegionOk = false;
- if (result.script.equals(value00.script)) { //script is default
- if (result.region.equals(value00.region)) {
- return new LSR(result.language, "", "", LSR.DONT_CARE_FLAGS);
- } else if (fieldToFavor == ULocale.Minimize.FAVOR_REGION) {
- return new LSR(result.language, "", result.region, LSR.DONT_CARE_FLAGS);
- } else {
- favorRegionOk = true;
- }
- }
-
- // The last case is not as easy to optimize.
- // Maybe do later, but for now use the straightforward code.
- LSR result2 = maximize(languageIn, scriptIn, "");
- if (result2.equals(result)) {
- return new LSR(result.language, result.script, "", LSR.DONT_CARE_FLAGS);
- } else if (favorRegionOk) {
- return new LSR(result.language, "", result.region, LSR.DONT_CARE_FLAGS);
- }
- return result;
- }
-
- private Map<String, LSR> getTable() {
- Map<String, LSR> map = new TreeMap<>();
- StringBuilder sb = new StringBuilder();
- for (BytesTrie.Entry entry : trie) {
- sb.setLength(0);
- int length = entry.bytesLength();
- for (int i = 0; i < length;) {
- byte b = entry.byteAt(i++);
- if (b == '*') {
- sb.append("*-");
- } else if (b >= 0) {
- sb.append((char) b);
- } else { // end of subtag
- sb.append((char) (b & 0x7f)).append('-');
- }
- }
- assert sb.length() > 0 && sb.charAt(sb.length() - 1) == '-';
- sb.setLength(sb.length() - 1);
- map.put(sb.toString(), lsrs[entry.value]);
- }
- return map;
- }
-
- @Override
- public String toString() {
- return getTable().toString();
- }
-}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/ConstantAffixModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/ConstantAffixModifier.java
index e3c970c..349b326 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/ConstantAffixModifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/ConstantAffixModifier.java
@@ -91,7 +91,7 @@
}
@Override
- public boolean semanticallyEquivalent(Modifier other) {
+ public boolean strictEquals(Modifier other) {
if (!(other instanceof ConstantAffixModifier)) {
return false;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java
index 19b21d3..a9b780b 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java
@@ -98,14 +98,11 @@
}
@Override
- public boolean semanticallyEquivalent(Modifier other) {
+ public boolean strictEquals(Modifier other) {
if (!(other instanceof ConstantMultiFieldModifier)) {
return false;
}
ConstantMultiFieldModifier _other = (ConstantMultiFieldModifier) other;
- if (parameters != null && _other.parameters != null && parameters.obj == _other.parameters.obj) {
- return true;
- }
return Arrays.equals(prefixChars, _other.prefixChars) && Arrays.equals(prefixFields, _other.prefixFields)
&& Arrays.equals(suffixChars, _other.suffixChars) && Arrays.equals(suffixFields, _other.suffixFields)
&& overwrite == _other.overwrite && strong == _other.strong;
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java
index 6e24f96..b75ae8c 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java
@@ -720,7 +720,7 @@
StringBuilder sb = new StringBuilder();
toScientificString(sb);
- return Double.valueOf(sb.toString());
+ return Double.parseDouble(sb.toString());
}
@Override
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/Grouper.java b/android_icu4j/src/main/java/android/icu/impl/number/Grouper.java
index 932a814..b706bcb 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/Grouper.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/Grouper.java
@@ -82,7 +82,7 @@
ICUResourceBundle resource = (ICUResourceBundle) UResourceBundle
.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
String result = resource.getStringWithFallback("NumberElements/minimumGroupingDigits");
- return Short.valueOf(result);
+ return Short.parseShort(result);
}
/**
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java b/android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java
index 39bb3e9..8ba56ba 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/LongNameHandler.java
@@ -1301,8 +1301,8 @@
}
/**
- * Sets modifiers to a combination of `leadFormats` (one per plural form)
- * and `trailFormat` appended to each.
+ * Sets modifiers to a combination of {@code leadFormats} (one per plural form)
+ * and {@code trailFormat} appended to each.
*
* With a leadFormat of "{0}m" and a trailFormat of "{0}/s", it produces a
* pattern of "{0}m/s" by inserting each leadFormat pattern into
@@ -1342,9 +1342,9 @@
}
/**
- * Produces a plural-appropriate Modifier for a unit: `quantity` is taken as
+ * Produces a plural-appropriate Modifier for a unit: {@code quantity} is taken as
* the final smallest unit, while the larger unit values must be provided
- * via `micros.mixedMeasures`.
+ * via {@code micros.mixedMeasures}.
*
* Does not call parent.processQuantity, so cannot get a MicroProps instance
* that way. Instead, the instance is passed in as a parameter.
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/MicroProps.java b/android_icu4j/src/main/java/android/icu/impl/number/MicroProps.java
index be75b29..4fd22d6 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/MicroProps.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/MicroProps.java
@@ -82,8 +82,8 @@
/**
* As MicroProps is the "base instance", this implementation of
- * MircoPropsGenerator.processQuantity() just ensures that the output
- * `micros` is correctly initialized.
+ * {@code MircoPropsGenerator.processQuantity()} just ensures that the output
+ * {@code micros} is correctly initialized.
* <p>
* For the "safe" invocation of this function, micros must not be *this,
* such that a copy of the base instance is made. For the "unsafe" path,
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/MixedUnitLongNameHandler.java b/android_icu4j/src/main/java/android/icu/impl/number/MixedUnitLongNameHandler.java
index 1d21c87..90b46d6 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/MixedUnitLongNameHandler.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/MixedUnitLongNameHandler.java
@@ -105,9 +105,9 @@
}
/**
- * Produces a plural-appropriate Modifier for a mixed unit: `quantity` is
+ * Produces a plural-appropriate Modifier for a mixed unit: {@code quantity} is
* taken as the final smallest unit, while the larger unit values must be
- * provided by `micros.mixedMeasures`, micros being the MicroProps instance
+ * provided by {@code micros.mixedMeasures}, micros being the MicroProps instance
* returned by the parent.
*
* This function must not be called if this instance has no parent: call
@@ -123,9 +123,9 @@
}
/**
- * Produces a plural-appropriate Modifier for a mixed unit: `quantity` is
+ * Produces a plural-appropriate Modifier for a mixed unit: {@code quantity} is
* taken as the final smallest unit, while the larger unit values must be
- * provided via `micros.mixedMeasures`.
+ * provided via {@code micros.mixedMeasures}.
*
* Does not call parent.processQuantity, so cannot get a MicroProps instance
* that way. Instead, the instance is passed in as a parameter.
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java b/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java
index db6c27d..08a4156 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java
@@ -26,6 +26,7 @@
POS;
static final int COUNT = Signum.values().length;
+ public static final Signum[] VALUES = Signum.values();
};
/**
@@ -87,8 +88,45 @@
public Parameters getParameters();
/**
+ * Returns whether this Modifier equals another Modifier.
+ */
+ public boolean strictEquals(Modifier other);
+
+ /**
* Returns whether this Modifier is *semantically equivalent* to the other Modifier;
* in many cases, this is the same as equal, but parameters should be ignored.
*/
- public boolean semanticallyEquivalent(Modifier other);
+ default boolean semanticallyEquivalent(Modifier other) {
+ Parameters paramsThis = this.getParameters();
+ Parameters paramsOther = other.getParameters();
+ if (paramsThis == null && paramsOther == null) {
+ return this.strictEquals(other);
+ } else if (paramsThis == null || paramsOther == null) {
+ return false;
+ } else if (paramsThis.obj == null && paramsOther.obj == null) {
+ return this.strictEquals(other);
+ } else if (paramsThis.obj == null || paramsOther.obj == null) {
+ return false;
+ }
+ for (Signum signum : Signum.VALUES) {
+ for (StandardPlural plural : StandardPlural.VALUES) {
+ Modifier mod1 = paramsThis.obj.getModifier(signum, plural);
+ Modifier mod2 = paramsOther.obj.getModifier(signum, plural);
+ if (mod1 == mod2) {
+ // Equal pointers
+ continue;
+ } else if (mod1 == null || mod2 == null) {
+ // One pointer is null but not the other
+ return false;
+ } else if (!mod1.strictEquals(mod2)) {
+ // The modifiers are NOT equivalent
+ return false;
+ } else {
+ // The modifiers are equivalent
+ continue;
+ }
+ }
+ }
+ return true;
+ }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java
index e84af1e..b4bce00 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java
@@ -352,7 +352,7 @@
}
@Override
- public boolean semanticallyEquivalent(Modifier other) {
+ public boolean strictEquals(Modifier other) {
// This method is not currently used. (unsafe path not used in range formatting)
assert false;
return false;
diff --git a/android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java
index 8a34133..34ad9f8 100644
--- a/android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java
+++ b/android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java
@@ -73,14 +73,11 @@
}
@Override
- public boolean semanticallyEquivalent(Modifier other) {
+ public boolean strictEquals(Modifier other) {
if (!(other instanceof SimpleModifier)) {
return false;
}
SimpleModifier _other = (SimpleModifier) other;
- if (parameters != null && _other.parameters != null && parameters.obj == _other.parameters.obj) {
- return true;
- }
return compiledPattern.equals(_other.compiledPattern) && field == _other.field && strong == _other.strong;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/personname/FieldModifierImpl.java b/android_icu4j/src/main/java/android/icu/impl/personname/FieldModifierImpl.java
index 6d0918c..3d0e6bd 100644
--- a/android_icu4j/src/main/java/android/icu/impl/personname/FieldModifierImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/personname/FieldModifierImpl.java
@@ -4,7 +4,6 @@
package android.icu.impl.personname;
import java.util.Locale;
-import java.util.StringTokenizer;
import android.icu.lang.UCharacter;
import android.icu.text.BreakIterator;
@@ -31,9 +30,18 @@
case INITIAL_CAP:
return new InitialCapModifier(formatterImpl.getLocale());
case INITIAL:
- return new InitialModifier(formatterImpl.getInitialPattern(), formatterImpl.getInitialSequencePattern());
+ return new InitialModifier(formatterImpl.getLocale(), formatterImpl.getInitialPattern(), formatterImpl.getInitialSequencePattern());
+ case RETAIN:
+ // "retain" is handled by InitialMofidier and PersonNamePattern.NameFieldImpl
+ return NOOP_MODIFIER;
case MONOGRAM:
return MONOGRAM_MODIFIER;
+ case GENITIVE:
+ // no built-in implementation for deriving genitive from nominative; PersonName object must supply
+ return NOOP_MODIFIER;
+ case VOCATIVE:
+ // no built-in implementation for deriving vocative from nominative; PersonName object must supply
+ return NOOP_MODIFIER;
default:
throw new IllegalArgumentException("Invalid modifier ID " + modifierID);
}
@@ -104,25 +112,47 @@
* (In English, these patterns put periods after each initial and connect them with spaces.)
* This is default behavior of the "initial" modifier.
*/
- private static class InitialModifier extends FieldModifierImpl {
+ static class InitialModifier extends FieldModifierImpl {
+ private final Locale locale;
private final SimpleFormatter initialFormatter;
private final SimpleFormatter initialSequenceFormatter;
+ private boolean retainPunctuation;
- public InitialModifier(String initialPattern, String initialSequencePattern) {
+ public InitialModifier(Locale locale, String initialPattern, String initialSequencePattern) {
+ this.locale = locale;
this.initialFormatter = SimpleFormatter.compile(initialPattern);
this.initialSequenceFormatter = SimpleFormatter.compile(initialSequencePattern);
+ this.retainPunctuation = false;
+ }
+
+ public void setRetainPunctuation(boolean retain) {
+ this.retainPunctuation = retain;
}
@Override
public String modifyField(String fieldValue) {
+ String separator = "";
String result = null;
- StringTokenizer tok = new StringTokenizer(fieldValue, " ");
- while (tok.hasMoreTokens()) {
- String curInitial = getFirstGrapheme(tok.nextToken());
- if (result == null) {
- result = initialFormatter.format(curInitial);
+ BreakIterator bi = BreakIterator.getWordInstance(locale);
+ bi.setText(fieldValue);
+ int wordStart = bi.first();
+ for (int wordEnd = bi.next(); wordEnd != BreakIterator.DONE; wordStart = wordEnd, wordEnd = bi.next()) {
+ String word = fieldValue.substring(wordStart, wordEnd);
+ if (Character.isLetter(word.charAt(0))) {
+ String curInitial = getFirstGrapheme(word);
+ if (result == null) {
+ result = initialFormatter.format(curInitial);
+ } else if (retainPunctuation) {
+ result = result + separator + initialFormatter.format(curInitial);
+ separator = "";
+ } else {
+ result = initialSequenceFormatter.format(result, initialFormatter.format(curInitial));
+ }
+ } else if (Character.isWhitespace(word.charAt(0))) {
+ // coalesce a sequence of whitespace characters down to a single space
+ separator = separator + word.charAt(0);
} else {
- result = initialSequenceFormatter.format(result, initialFormatter.format(curInitial));
+ separator = separator + word;
}
}
return result;
diff --git a/android_icu4j/src/main/java/android/icu/impl/personname/PersonNameFormatterImpl.java b/android_icu4j/src/main/java/android/icu/impl/personname/PersonNameFormatterImpl.java
index 98473da..a17a3d1 100644
--- a/android_icu4j/src/main/java/android/icu/impl/personname/PersonNameFormatterImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/personname/PersonNameFormatterImpl.java
@@ -34,43 +34,39 @@
private final String initialSequencePattern;
private final boolean capitalizeSurname;
private final String foreignSpaceReplacement;
- private final boolean formatterLocaleUsesSpaces;
+ private final String nativeSpaceReplacement;
private final PersonNameFormatter.Length length;
private final PersonNameFormatter.Usage usage;
private final PersonNameFormatter.Formality formality;
- private final Set<PersonNameFormatter.Options> options;
+ private final PersonNameFormatter.DisplayOrder displayOrder;
public PersonNameFormatterImpl(Locale locale,
PersonNameFormatter.Length length,
PersonNameFormatter.Usage usage,
PersonNameFormatter.Formality formality,
- Set<PersonNameFormatter.Options> options) {
- // null for `options` is the same as the empty set
- if (options == null) {
- options = new HashSet<>();
- }
-
+ PersonNameFormatter.DisplayOrder displayOrder,
+ boolean surnameAllCaps) {
// save off our creation parameters (these are only used if we have to create a second formatter)
this.length = length;
this.usage = usage;
this.formality = formality;
- this.options = options;
+ this.displayOrder = displayOrder;
+ this.capitalizeSurname = surnameAllCaps;
// load simple property values from the resource bundle (or the options set)
ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
this.locale = locale;
this.initialPattern = rb.getStringWithFallback("personNames/initialPattern/initial");
this.initialSequencePattern = rb.getStringWithFallback("personNames/initialPattern/initialSequence");
- this.capitalizeSurname = options.contains(PersonNameFormatter.Options.SURNAME_ALLCAPS);
this.foreignSpaceReplacement = rb.getStringWithFallback("personNames/foreignSpaceReplacement");
- this.formatterLocaleUsesSpaces = !LOCALES_THAT_DONT_USE_SPACES.contains(locale.getLanguage());
+ this.nativeSpaceReplacement = rb.getStringWithFallback("personNames/nativeSpaceReplacement");
// asjust for combinations of parameters that don't make sense in practice
if (usage == PersonNameFormatter.Usage.MONOGRAM) {
// we don't support SORTING in conjunction with MONOGRAM; if the caller passes in SORTING, remove it from
// the options list
- options.remove(PersonNameFormatter.Options.SORTING);
- } else if (options.contains(PersonNameFormatter.Options.SORTING)) {
+ displayOrder = PersonNameFormatter.DisplayOrder.DEFAULT;
+ } else if (displayOrder == PersonNameFormatter.DisplayOrder.SORTING) {
// we only support SORTING in conjunction with REFERRING; if the caller passes in ADDRESSING, treat it
// the same as REFERRING
usage = PersonNameFormatter.Usage.REFERRING;
@@ -80,9 +76,12 @@
// different for different names), load patterns for both given-first and surname-first names. (If the user has
// specified SORTING, we don't need to do this-- we just load the "sorting" patterns and ignore the name's order.)
final String RESOURCE_PATH_PREFIX = "personNames/namePattern/";
- String resourceNameBody = length.toString().toLowerCase() + "-" + usage.toString().toLowerCase() + "-"
- + formality.toString().toLowerCase();
- if (!options.contains(PersonNameFormatter.Options.SORTING)) {
+ String lengthStr = (length != PersonNameFormatter.Length.DEFAULT) ? length.toString().toLowerCase()
+ : rb.getStringWithFallback("personNames/parameterDefault/length");
+ String formalityStr = (formality != PersonNameFormatter.Formality.DEFAULT) ? formality.toString().toLowerCase()
+ : rb.getStringWithFallback("personNames/parameterDefault/formality");
+ String resourceNameBody = lengthStr + "-" + usage.toString().toLowerCase() + "-" + formalityStr;
+ if (displayOrder != PersonNameFormatter.DisplayOrder.SORTING) {
ICUResourceBundle gnFirstResource = rb.getWithFallback(RESOURCE_PATH_PREFIX + "givenFirst-" + resourceNameBody);
ICUResourceBundle snFirstResource = rb.getWithFallback(RESOURCE_PATH_PREFIX + "surnameFirst-" + resourceNameBody);
@@ -106,54 +105,80 @@
/**
* THIS IS A DUMMY CONSTRUCTOR JUST FOR THE USE OF THE UNIT TESTS TO CHECK SOME OF THE INTERNAL IMPLEMENTATION!
*/
- public PersonNameFormatterImpl(Locale locale, String[] patterns) {
+ public PersonNameFormatterImpl(Locale locale, String[] gnFirstPatterns, String[] snFirstPatterns, String[] gnFirstLocales, String[] snFirstLocales) {
// first, set dummy values for the other fields
- snFirstPatterns = null;
- gnFirstLocales = null;
- snFirstLocales = null;
length = PersonNameFormatter.Length.MEDIUM;
usage = PersonNameFormatter.Usage.REFERRING;
formality = PersonNameFormatter.Formality.FORMAL;
- options = Collections.emptySet();
+ displayOrder = PersonNameFormatter.DisplayOrder.DEFAULT;
initialPattern = "{0}.";
initialSequencePattern = "{0} {1}";
capitalizeSurname = false;
foreignSpaceReplacement = " ";
- formatterLocaleUsesSpaces = true;
+ nativeSpaceReplacement = " ";
- // then, set values for the fields we actually care about
+ // then, set values for the fields we actually care about (all but gnFirstPatterns are optional)
this.locale = locale;
- gnFirstPatterns = PersonNamePattern.makePatterns(patterns, this);
+ this.gnFirstPatterns = PersonNamePattern.makePatterns(gnFirstPatterns, this);
+ this.snFirstPatterns = (snFirstPatterns != null) ? PersonNamePattern.makePatterns(snFirstPatterns, this) : null;
+ if (gnFirstLocales != null) {
+ this.gnFirstLocales = new HashSet<>();
+ Collections.addAll(this.gnFirstLocales, gnFirstLocales);
+ } else {
+ this.gnFirstLocales = null;
+ }
+ if (snFirstLocales != null) {
+ this.snFirstLocales = new HashSet<>();
+ Collections.addAll(this.snFirstLocales, snFirstLocales);
+ } else {
+ this.snFirstLocales = null;
+ }
+ }
+ @Override
+ public String toString() {
+ return "PersonNameFormatter: " + displayOrder + "-" + length + "-" + usage + "-" + formality + ", " + locale;
}
public String formatToString(PersonName name) {
// TODO: Should probably return a FormattedPersonName object
- // if the formatter is for a language that doesn't use spaces between words and the name is from a language
- // that does, create a formatter for the NAME'S locale and use THAT to format the name
Locale nameLocale = getNameLocale(name);
- boolean nameLocaleUsesSpaces = !LOCALES_THAT_DONT_USE_SPACES.contains(nameLocale.getLanguage());
- if (!formatterLocaleUsesSpaces && nameLocaleUsesSpaces) {
- PersonNameFormatterImpl nativeFormatter = new PersonNameFormatterImpl(nameLocale, this.length,
- this.usage, this.formality, this.options);
- String result = nativeFormatter.formatToString(name);
+ String nameScript = getNameScript(name);
- // BUT, if the name is actually written in the formatter locale's script, replace any spaces in the name
- // with the foreignSpaceReplacement character
- if (!foreignSpaceReplacement.equals(" ") && scriptMatchesLocale(result, this.locale)) {
- result = result.replace(" ", this.foreignSpaceReplacement);
+ if (!nameScriptMatchesLocale(nameScript, this.locale)) {
+ Locale newFormattingLocale;
+ if (formattingLocaleExists(nameLocale)) {
+ newFormattingLocale = nameLocale;
+ } else {
+ newFormattingLocale = newLocaleWithScript(null, nameScript, nameLocale.getCountry());
}
- return result;
+ PersonNameFormatterImpl nameLocaleFormatter = new PersonNameFormatterImpl(newFormattingLocale, this.length,
+ this.usage, this.formality, this.displayOrder, this.capitalizeSurname);
+ return nameLocaleFormatter.formatToString(name);
}
- // if we get down to here, we're just doing normal formatting-- if we have both given-first and surname-first
- // rules, choose which one to use based on the name's locale and preferred field order
+ String result = null;
+
+ // choose the GN-first or SN-first pattern based on the name itself and use that to format it
if (snFirstPatterns == null || nameIsGnFirst(name)) {
- return getBestPattern(gnFirstPatterns, name).format(name);
+ result = getBestPattern(gnFirstPatterns, name).format(name);
} else {
- return getBestPattern(snFirstPatterns, name).format(name);
+ result = getBestPattern(snFirstPatterns, name).format(name);
}
+
+ // if either of the space-replacement characters is something other than a space,
+ // check to see if the name locale's language matches the formatter locale's language.
+ // If they match, replace all spaces with the native space-replacement character,
+ // and if they don't, replace all spaces with the foreign space-replacement character
+ if (!nativeSpaceReplacement.equals(" ") || !foreignSpaceReplacement.equals(" ")) {
+ if (localesMatch(nameLocale, this.locale)) {
+ result = result.replace(" ", nativeSpaceReplacement);
+ } else {
+ result = result.replace(" ", foreignSpaceReplacement);
+ }
+ }
+ return result;
}
public Locale getLocale() {
@@ -166,7 +191,8 @@
public PersonNameFormatter.Formality getFormality() { return formality; }
- public Set<PersonNameFormatter.Options> getOptions() { return options; }
+ public PersonNameFormatter.DisplayOrder getDisplayOrder() { return displayOrder; }
+ public boolean getSurnameAllCaps() { return capitalizeSurname; }
public String getInitialPattern() {
return initialPattern;
@@ -180,7 +206,7 @@
return capitalizeSurname;
}
- private final Set<String> LOCALES_THAT_DONT_USE_SPACES = new HashSet<>(Arrays.asList("ja", "zh", "th", "yue", "km", "lo"));
+ static final Set<String> NON_DEFAULT_SCRIPTS = new HashSet<>(Arrays.asList("Hani", "Hira", "Kana"));
/**
* Returns the value of the resource, as a string array.
@@ -205,36 +231,72 @@
* @return If true, use given-first order to format the name; if false, use surname-first order.
*/
private boolean nameIsGnFirst(PersonName name) {
- // the name can declare its order-- check that first (it overrides any locale-based calculation)
- Set<PersonName.FieldModifier> modifiers = new HashSet<>();
- String preferredOrder = name.getFieldValue(PersonName.NameField.PREFERRED_ORDER, modifiers);
- if (preferredOrder != null) {
- if (preferredOrder.equals("givenFirst")) {
- return true;
- } else if (preferredOrder.equals("surnameFirst")) {
- return false;
- } else {
- throw new IllegalArgumentException("Illegal preferredOrder value " + preferredOrder);
- }
+ // if the formatter has its display order set to one of the "force" values, that overrides
+ // all this logic and the name's preferred-order property
+ if (this.displayOrder == PersonNameFormatter.DisplayOrder.FORCE_GIVEN_FIRST) {
+ return true;
+ } else if (this.displayOrder == PersonNameFormatter.DisplayOrder.FORCE_SURNAME_FIRST) {
+ return false;
}
- String localeStr = getNameLocale(name).toString();
+ // the name can declare its order-- check that first (it overrides any locale-based calculation)
+ if (name.getPreferredOrder() == PersonName.PreferredOrder.GIVEN_FIRST) {
+ return true;
+ } else if (name.getPreferredOrder() == PersonName.PreferredOrder.SURNAME_FIRST) {
+ return false;
+ }
+
+ // Otherwise, search the gnFirstLocales and snFirstLocales for the locale's name.
+ // For our purposes, the "locale's name" is the locale the name itself gives us (if it
+ // has one), or the locale we guess for the name (if it doesn't).
+ Locale nameLocale = name.getNameLocale();
+ if (nameLocale == null) {
+ nameLocale = getNameLocale(name);
+ }
+
+ // this is a hack to deal with certain script codes that are valid, but not the default, for their locales--
+ // to make the parent-chain lookup work right, we need to replace any of those script codes (in the name's locale)
+ // with the appropriate default script for whatever language and region we have
+ ULocale nameULocale = ULocale.forLocale(nameLocale);
+ if (NON_DEFAULT_SCRIPTS.contains(nameULocale.getScript())) {
+ ULocale.Builder builder = new ULocale.Builder();
+ builder.setLocale(nameULocale);
+ builder.setScript(null);
+ nameULocale = ULocale.addLikelySubtags(builder.build());
+ }
+
+ // now search for the locale in the gnFirstLocales and snFirstLocales lists...
+ String localeStr = nameULocale.getName();
+ String origLocaleStr = localeStr;
+ String languageCode = nameULocale.getLanguage();
+
do {
+ // first check if the locale is in one of those lists
if (gnFirstLocales.contains(localeStr)) {
return true;
} else if (snFirstLocales.contains(localeStr)) {
return false;
}
- int lastUnderbarPos = localeStr.lastIndexOf("_");
- if (lastUnderbarPos >= 0) {
- localeStr = localeStr.substring(0, lastUnderbarPos);
- } else {
- localeStr = "root";
+ // if not, try again with "und" in place of the language code (this lets us use "und_CN" to match
+ // all locales with a region code of "CN" and makes sure the last thing we try is always "und", which
+ // is required to be in gnFirstLocales or snFirstLocales)
+ String undStr = localeStr.replaceAll("^" + languageCode, "und");
+ if (gnFirstLocales.contains(undStr)) {
+ return true;
+ } else if (snFirstLocales.contains(undStr)) {
+ return false;
}
- } while (!localeStr.equals("root"));
- // should never get here-- "root" should always be in one of the locales
+ // if we haven't found the locale ID yet, look up its parent locale ID and try again-- if getParentLocaleID()
+ // returns null (i.e., we have a locale ID, such as "zh_Hant", that inherits directly from "root"), try again
+ // with just the locale ID's language code (this fixes it so that "zh_Hant" matches "zh", even though "zh" isn't,
+ // strictly speaking, its parent locale)
+ String parentLocaleStr = ICUResourceBundle.getParentLocaleID(localeStr, origLocaleStr, ICUResourceBundle.OpenType.LOCALE_DEFAULT_ROOT);
+ localeStr = (parentLocaleStr != null) ? parentLocaleStr : languageCode;
+ } while (localeStr != null);
+
+ // should never get here ("und" should always be in gnFirstLocales or snFirstLocales), but if we do...
return true;
}
@@ -267,6 +329,67 @@
}
/**
+ * Internal function to figure out the name's script by examining its characters.
+ * @param name The name for which we need the script
+ * @return The four-letter script code for the name.
+ */
+ private String getNameScript(PersonName name) {
+ // Rather than exhaustively checking all the fields in the name, we just check the given-name
+ // and surname fields, giving preference to the script of the surname if they're different
+ // (we concatenate them into one string for simplicity). The "name script" is the script
+ // of the first character we find whose script isn't "common". If that script is one
+ // of the scripts used by the specified locale, we have a match.
+ String givenName = name.getFieldValue(PersonName.NameField.SURNAME, Collections.emptySet());
+ String surname = name.getFieldValue(PersonName.NameField.GIVEN, Collections.emptySet());
+ String nameText = ((surname != null) ? surname : "") + ((givenName != null) ? givenName : "");
+ int stringScript = UScript.UNKNOWN;
+ for (int i = 0; stringScript == UScript.UNKNOWN && i < nameText.length(); i++) {
+ int c = nameText.codePointAt(i);
+ int charScript = UScript.getScript(c);
+ if (charScript != UScript.COMMON && charScript != UScript.INHERITED && charScript != UScript.UNKNOWN) {
+ stringScript = charScript;
+ }
+ }
+ return UScript.getShortName(stringScript);
+ }
+
+ private Locale newLocaleWithScript(Locale oldLocale, String scriptCode, String regionCode) {
+ Locale workingLocale;
+ String localeScript;
+
+ // if we got the "unknown" script code, don't do anything with it-- just return the original locale
+ if (scriptCode.equals("Zzzz")) {
+ return oldLocale;
+ }
+
+ Locale.Builder builder = new Locale.Builder();
+ if (oldLocale != null) {
+ workingLocale = oldLocale;
+ builder.setLocale(oldLocale);
+ localeScript = ULocale.addLikelySubtags(ULocale.forLocale(oldLocale)).getScript();
+ } else {
+ ULocale tmpLocale = ULocale.addLikelySubtags(new ULocale("und_" + scriptCode));
+ builder.setLanguage(tmpLocale.getLanguage());
+ workingLocale = ULocale.addLikelySubtags(new ULocale(tmpLocale.getLanguage())).toLocale();
+ localeScript = workingLocale.getScript();
+
+ if (regionCode != null) {
+ builder.setRegion(regionCode);
+ }
+ }
+
+ // if the detected character script matches one of the default scripts for the name's locale,
+ // use the name locale's default script code in the locale ID we return (this converts a detected
+ // script of "Hani" to "Hans" for "zh", "Hant" for "zh_Hant", and "Jpan" for "ja")
+ if (!scriptCode.equals(localeScript) && nameScriptMatchesLocale(scriptCode, workingLocale)) {
+ scriptCode = localeScript;
+ }
+
+ builder.setScript(scriptCode);
+ return builder.build();
+ }
+
+ /**
* Internal function to figure out the name's locale when the name doesn't specify it.
* (Note that this code assumes that if the locale is specified, it includes a language
* code.)
@@ -274,57 +397,69 @@
* @return The name's (real or guessed) locale.
*/
private Locale getNameLocale(PersonName name) {
- // if the name specifies its locale, we can just return it
- Locale nameLocale = name.getNameLocale();
- if (nameLocale == null) {
- // if not, we look at the characters in the name. If their script matches the default script for the formatter's
- // locale, we use the formatter's locale as the name's locale
- int formatterScript = UScript.getCodeFromName(ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript());
- String givenName = name.getFieldValue(PersonName.NameField.GIVEN, new HashSet<PersonName.FieldModifier>());
- int nameScript = UScript.INVALID_CODE;
- for (int i = 0; nameScript == UScript.INVALID_CODE && i < givenName.length(); i++) {
- // the script of the name is the script of the first character in the name whose script isn't
- // COMMON or INHERITED
- int script = UScript.getScript(givenName.charAt(i));
- if (script != UScript.COMMON && script != UScript.INHERITED) {
- nameScript = script;
- }
- }
- if (formatterScript == nameScript) {
- nameLocale = this.locale;
- } else {
- // if the name's script is different from the formatter's script, we use addLikelySubtags() to find the
- // default language for the name's script and use THAT as the name's locale
- nameLocale = new Locale(ULocale.addLikelySubtags(new ULocale("und_" + UScript.getShortName(nameScript))).getLanguage());
- }
- // TODO: This algorithm has a few deficiencies: First, it assumes the script of the string is the script of the first
- // character in the string that's not COMMON or INHERITED. This won't work well for some languages, such as Japanese,
- // that use multiple scripts. Doing better would require adding a new getScript(String) method on UScript, which
- // might be something we want. Second, we only look at the given-name field. This field should always be populated,
- // but if it isn't, we're stuck. Looking at all the fields requires API on PersonName that we don't need anywhere
- // else.
- }
- return nameLocale;
+ return newLocaleWithScript(name.getNameLocale(), getNameScript(name), null);
}
/**
- * Returns true if the script of `s` is one of the default scripts for `locale`.
- * This function only checks the script of the first character whose script isn't "common,"
- * so it probably won't work right on mixed-script strings.
+ * Returns true if the characters in the name match one of the scripts for the specified locale.
*/
- private boolean scriptMatchesLocale(String s, Locale locale) {
- int[] localeScripts = UScript.getCode(locale);
- int stringScript = UScript.COMMON;
- for (int i = 0; stringScript == UScript.COMMON && i < s.length(); i++) {
- char c = s.charAt(i);
- stringScript = UScript.getScript(c);
+ private boolean nameScriptMatchesLocale(String nameScriptID, Locale formatterLocale) {
+ // if the script code is the "unknown" script, pretend it matches everything
+ if (nameScriptID.equals("Zzzz")) {
+ return true;
}
+ int[] localeScripts = UScript.getCode(formatterLocale);
+ int nameScript = UScript.getCodeFromName(nameScriptID);
+
for (int localeScript : localeScripts) {
- if (localeScript == stringScript) {
+ if (localeScript == nameScript || (localeScript == UScript.SIMPLIFIED_HAN && nameScript == UScript.HAN) || (localeScript == UScript.TRADITIONAL_HAN && nameScript == UScript.HAN)) {
return true;
}
}
return false;
}
+
+ /**
+ * Returns true if there's actual name formatting data for the specified locale (i.e., when
+ * we fetch the resource data, we don't fall back to root).
+ */
+ private boolean formattingLocaleExists(Locale formattingLocale) {
+ // NOTE: What we really want to test for here is whether we're falling back to root for either the resource bundle itself
+ // or for the personNames/nameOrderLocales/givenFirst and personNames/nameOrderLocales/surnameFirst resources.
+ // The problem is that getBundleInstance() doesn't return root when it can't find what it's looking for; it returns
+ // ULocale.getDefault(). We could theoretically get around this by passing OpenType.LOCALE_ROOT, but this
+ // bypasses the parent-locale table, so fallback across script can happen (ja_Latn falls back to ja instead of root).
+ // So I'm checking to see if the language code got changed and using that as a surrogate for falling back to root.
+ String formattingLanguage = formattingLocale.getLanguage();
+ ICUResourceBundle mainRB = ICUResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, ULocale.forLocale(formattingLocale), ICUResourceBundle.OpenType.LOCALE_DEFAULT_ROOT);
+ if (!mainRB.getULocale().getLanguage().equals(formattingLanguage)) {
+ return false;
+ }
+
+ ICUResourceBundle gnFirstResource = mainRB.getWithFallback("personNames/nameOrderLocales/givenFirst");
+ ICUResourceBundle snFirstResource = mainRB.getWithFallback("personNames/nameOrderLocales/surnameFirst");
+
+ return gnFirstResource.getULocale().getLanguage().equals(formattingLanguage) || snFirstResource.getULocale().getLanguage().equals(formattingLanguage);
+ }
+
+ /**
+ * Returns true if the two locales should be considered equivalent for space-replacement purposes.
+ */
+ private boolean localesMatch(Locale nameLocale, Locale formatterLocale) {
+ String nameLanguage = nameLocale.getLanguage();
+ String formatterLanguage = formatterLocale.getLanguage();
+
+ if (nameLanguage.equals(formatterLanguage)) {
+ return true;
+ }
+
+ // HACK to make Japanese and Chinese names use the native format and native space replacement
+ // (do we want to do something more general here?)
+ if ((nameLanguage.equals("ja") || nameLanguage.equals("zh")) && (formatterLanguage.equals("ja") || formatterLanguage.equals("zh"))) {
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/personname/PersonNamePattern.java b/android_icu4j/src/main/java/android/icu/impl/personname/PersonNamePattern.java
index d612454..f24e2f2 100644
--- a/android_icu4j/src/main/java/android/icu/impl/personname/PersonNamePattern.java
+++ b/android_icu4j/src/main/java/android/icu/impl/personname/PersonNamePattern.java
@@ -4,9 +4,11 @@
package android.icu.impl.personname;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
@@ -28,6 +30,11 @@
return result;
}
+ @Override
+ public String toString() {
+ return patternText;
+ }
+
private PersonNamePattern(String patternText, PersonNameFormatterImpl formatterImpl) {
this.patternText = patternText;
@@ -89,6 +96,11 @@
StringBuilder textBefore = new StringBuilder();
StringBuilder textAfter = new StringBuilder();
+ // if the name doesn't have a surname field and the pattern doesn't have a given-name field,
+ // we actually format a modified version of the name object where the contents of the
+ // given-name field has been copied into the surname field
+ name = hackNameForEmptyFields(name);
+
// the logic below attempts to implement the following algorithm:
// - If one or more fields at the beginning of the name are empty, also skip all literal text
// from the beginning of the name up to the first populated field.
@@ -149,7 +161,7 @@
public int numEmptyFields(PersonName name) {
int result = 0;
for (Element element : patternElements) {
- result += element.isPopulated(name) ? 0 : 1;
+ result += (!element.isLiteral() && !element.isPopulated(name)) ? 1 : 0;
}
return result;
}
@@ -162,6 +174,11 @@
* @param s2 The literal text after the omitted field.
*/
private String coalesce(StringBuilder s1, StringBuilder s2) {
+ // if the contents of s2 occur at the end of s1, we just use s1
+ if (endsWith(s1, s2)) {
+ s2.setLength(0);
+ }
+
// get the range of non-whitespace characters at the beginning of s1
int p1 = 0;
while (p1 < s1.length() && !Character.isWhitespace(s1.charAt(p1))) {
@@ -193,6 +210,45 @@
}
/**
+ * Returns true if s1 ends with s2.
+ */
+ private boolean endsWith(StringBuilder s1, StringBuilder s2) {
+ int p1 = s1.length() - 1;
+ int p2 = s2.length() - 1;
+
+ while (p1 >= 0 && p2 >= 0 && s1.charAt(p1) == s2.charAt(p2)) {
+ --p1;
+ --p2;
+ }
+ return p2 < 0;
+ }
+
+ private PersonName hackNameForEmptyFields(PersonName originalName) {
+ // this is a hack to deal with mononyms (name objects that don't have both a given name and a surname)--
+ // if the name object has a given-name field but not a surname field and the pattern either doesn't
+ // have a given-name field or only has "{given-initial}", we return a PersonName object that will
+ // return the value of the given-name field when asked for the value of the surname field and that
+ // will return null when asked for the value of the given-name field (all other field values and
+ // properties of the underlying object are returned unchanged)
+ PersonName result = originalName;
+ if (originalName.getFieldValue(PersonName.NameField.SURNAME, Collections.emptySet()) == null) {
+ boolean patternHasNonInitialGivenName = false;
+ for (PersonNamePattern.Element element : patternElements) {
+ if (!element.isLiteral()
+ && ((NameFieldImpl)element).fieldID == PersonName.NameField.GIVEN
+ && !((NameFieldImpl)element).modifiers.containsKey(PersonName.FieldModifier.INITIAL)) {
+ patternHasNonInitialGivenName = true;
+ break;
+ }
+ }
+ if (!patternHasNonInitialGivenName) {
+ return new GivenToSurnamePersonName(originalName);
+ }
+ }
+ return result;
+ }
+
+ /**
* A single element in a NamePattern. This is either a name field or a range of literal text.
*/
private interface Element {
@@ -211,6 +267,11 @@
this.text = text;
}
+ @Override
+ public String toString() {
+ return text;
+ }
+
public boolean isLiteral() {
return true;
}
@@ -249,6 +310,26 @@
for (PersonName.FieldModifier modifierID : modifierIDs) {
this.modifiers.put(modifierID, FieldModifierImpl.forName(modifierID, formatterImpl));
}
+
+ if (this.modifiers.containsKey(PersonName.FieldModifier.RETAIN)
+ && this.modifiers.containsKey(PersonName.FieldModifier.INITIAL)) {
+ FieldModifierImpl.InitialModifier initialModifier
+ = (FieldModifierImpl.InitialModifier) this.modifiers.get(PersonName.FieldModifier.INITIAL);
+ initialModifier.setRetainPunctuation(true);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ sb.append(fieldID);
+ for (PersonName.FieldModifier modifier : modifiers.keySet()) {
+ sb.append("-");
+ sb.append(modifier.toString());
+ }
+ sb.append("}");
+ return sb.toString();
}
public boolean isLiteral() {
@@ -267,10 +348,48 @@
}
public boolean isPopulated(PersonName name) {
- // just check whether the unmodified field contains a value
- Set<PersonName.FieldModifier> modifierIDs = new HashSet<>();
- String fieldValue = name.getFieldValue(fieldID, modifierIDs);
- return fieldValue != null && !fieldValue.isEmpty();
+ String result = this.format(name);
+ return result != null && ! result.isEmpty();
+ }
+ }
+
+ /**
+ * Internal class used when formatting a mononym (a PersonName object that only has
+ * a given-name field). If the name doesn't have a surname field and the pattern
+ * doesn't have a given-name field (or only has one that produces an initial), we
+ * use this class to behave as though the value supplied in the given-name field
+ * had instead been supplied in the surname field.
+ */
+ private static class GivenToSurnamePersonName implements PersonName {
+ private PersonName underlyingPersonName;
+
+ public GivenToSurnamePersonName(PersonName underlyingPersonName) {
+ this.underlyingPersonName = underlyingPersonName;
+ }
+
+ @Override
+ public String toString() {
+ return "Inverted version of " + underlyingPersonName.toString();
+ }
+ @Override
+ public Locale getNameLocale() {
+ return underlyingPersonName.getNameLocale();
+ }
+
+ @Override
+ public PreferredOrder getPreferredOrder() {
+ return underlyingPersonName.getPreferredOrder();
+ }
+
+ @Override
+ public String getFieldValue(NameField identifier, Set<FieldModifier> modifiers) {
+ if (identifier == NameField.SURNAME) {
+ return underlyingPersonName.getFieldValue(NameField.GIVEN, modifiers);
+ } else if (identifier == NameField.GIVEN) {
+ return null;
+ } else {
+ return underlyingPersonName.getFieldValue(identifier, modifiers);
+ }
}
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/ComplexUnitsConverter.java b/android_icu4j/src/main/java/android/icu/impl/units/ComplexUnitsConverter.java
index 4e614ac..6778d77 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/ComplexUnitsConverter.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/ComplexUnitsConverter.java
@@ -16,7 +16,8 @@
import android.icu.util.Measure;
/**
- * Converts from single or compound unit to single, compound or mixed units. For example, from `meter` to `foot+inch`.
+ * Converts from single or compound unit to single, compound or mixed units. For example, from {@code meter}
+ * to {@code foot+inch}.
* <p>
* DESIGN: This class uses <code>UnitsConverter</code> in order to perform the single converter (i.e. converters from
* a single unit to another single unit). Therefore, <code>ComplexUnitsConverter</code> class contains multiple
@@ -137,11 +138,12 @@
}
/**
- * Returns true if the specified `quantity` of the `inputUnit`, expressed in terms of the biggest unit in the
- * MeasureUnit `outputUnit`, is greater than or equal to `limit`.
+ * Returns true if the specified {@code quantity} of the {@code inputUnit}, expressed in terms of the biggest
+ * unit in the MeasureUnit {@code outputUnit}, is greater than or equal to {@code limit}.
* <p>
- * For example, if the input unit is `meter` and the target unit is `foot+inch`. Therefore, this function will
- * convert the `quantity` from `meter` to `foot`, then, it will compare the value in `foot` with the `limit`.
+ * For example, if the input unit is {@code meter} and the target unit is {@code foot+inch}. Therefore,
+ * this function will convert the {@code quantity} from {@code meter} to {@code foot}, then, it will
+ * compare the value in {@code foot} with the {@code limit}.
*/
public boolean greaterThanOrEqual(BigDecimal quantity, BigDecimal limit) {
assert !units_.isEmpty();
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java b/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java
index 4e6c60f..bc179c6 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java
@@ -12,6 +12,7 @@
import android.icu.impl.ICUData;
import android.icu.impl.ICUResourceBundle;
+import android.icu.impl.IllegalIcuArgumentException;
import android.icu.impl.UResource;
import android.icu.util.MeasureUnit;
import android.icu.util.UResourceBundle;
@@ -36,7 +37,7 @@
}
/**
- * Extracts the factor from a `SingleUnitImpl` to its Basic Unit.
+ * Extracts the factor from a {@code SingleUnitImpl} to its Basic Unit.
*
* @param singleUnit
* @return
@@ -81,6 +82,15 @@
}
+ // Map the MeasureUnitImpl for a simple unit to its corresponding SimpleUnitID,
+ // then get the specialMappingName for that SimpleUnitID (which may be null if
+ // the simple unit converts to base using factor + offset instelad of a special mapping).
+ protected String getSpecialMappingName(MeasureUnitImpl simpleUnit) {
+ if (!checkSimpleUnit(simpleUnit)) return null;
+ String simpleIdentifier = simpleUnit.getSingleUnits().get(0).getSimpleUnitID();
+ return this.mapToConversionRate.get(simpleIdentifier).getSpecialMappingName();
+ }
+
public MeasureUnitImpl extractCompoundBaseUnit(MeasureUnitImpl measureUnit) {
ArrayList<SingleUnitImpl> baseUnits = this.extractBaseUnits(measureUnit);
@@ -106,7 +116,7 @@
/**
* @param singleUnit An instance of SingleUnitImpl.
- * @return The base units in the `SingleUnitImpl` with applying the dimensionality only and not the SI prefix.
+ * @return The base units in the {@code SingleUnitImpl} with applying the dimensionality only and not the SI prefix.
* <p>
* NOTE:
* This method is helpful when checking the convertibility because no need to check convertibility.
@@ -124,10 +134,17 @@
}
/**
- * Checks if the `MeasureUnitImpl` is simple or not.
+ * @return The measurement systems for the specified unit.
+ */
+ public String extractSystems(SingleUnitImpl singleUnit) {
+ return mapToConversionRate.get(singleUnit.getSimpleUnitID()).getSystems();
+ }
+
+ /**
+ * Checks if the {@code MeasureUnitImpl} is simple or not.
*
* @param measureUnitImpl
- * @return true if the `MeasureUnitImpl` is simple, false otherwise.
+ * @return true if the {@code MeasureUnitImpl} is simple, false otherwise.
*/
private boolean checkSimpleUnit(MeasureUnitImpl measureUnitImpl) {
if (measureUnitImpl.getComplexity() != MeasureUnit.Complexity.SINGLE) return false;
@@ -162,6 +179,8 @@
String target = null;
String factor = null;
String offset = "0";
+ String special = null;
+ String systems = null;
for (int j = 0; simpleUnitConversionInfo.getKeyAndValue(j, key, value); j++) {
assert (value.getType() == UResourceBundle.STRING);
@@ -174,16 +193,20 @@
factor = valueString;
} else if ("offset".equals(keyString)) {
offset = valueString;
+ } else if ("special".equals(keyString)) {
+ special = valueString; // the name of a special mapping used instead of factor + optional offset.
+ } else if ("systems".equals(keyString)) {
+ systems = value.toString(); // still want the spaces here
} else {
- assert false : "The key must be target, factor or offset";
+ assert false : "The key must be target, factor, offset, special, or systems";
}
}
// HERE a single conversion rate data should be loaded
assert (target != null);
- assert (factor != null);
+ assert (factor != null || special != null);
- mapToConversionRate.put(simpleUnit, new ConversionRateInfo(simpleUnit, target, factor, offset));
+ mapToConversionRate.put(simpleUnit, new ConversionRateInfo(simpleUnit, target, factor, offset, special, systems));
}
@@ -204,12 +227,16 @@
private final String target;
private final String conversionRate;
private final BigDecimal offset;
+ private final String specialMappingName; // the name of a special mapping used instead of factor + optional offset.
+ private final String systems;
- public ConversionRateInfo(String simpleUnit, String target, String conversionRate, String offset) {
+ public ConversionRateInfo(String simpleUnit, String target, String conversionRate, String offset, String special, String systems) {
this.simpleUnit = simpleUnit;
this.target = target;
this.conversionRate = conversionRate;
this.offset = forNumberWithDivision(offset);
+ this.specialMappingName = special;
+ this.systems = systems;
}
private static BigDecimal forNumberWithDivision(String numberWithDivision) {
@@ -244,7 +271,24 @@
* @return The conversion rate from this unit to the base unit.
*/
public String getConversionRate() {
+ if (conversionRate==null) {
+ throw new IllegalIcuArgumentException("trying to use a null conversion rate (for special?)");
+ }
return conversionRate;
}
+
+ /**
+ * @return The name of the special conversion system for this unit (used instead of factor + optional offset).
+ */
+ public String getSpecialMappingName() {
+ return specialMappingName;
+ }
+
+ /**
+ * @return The measurement systems this unit belongs to.
+ */
+ public String getSystems() {
+ return systems;
+ }
}
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java b/android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java
index 1277b1b..975665b 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java
@@ -799,6 +799,20 @@
@Override
public int compare(MeasureUnitImpl o1, MeasureUnitImpl o2) {
+ String special1 = this.conversionRates.getSpecialMappingName(o1);
+ String special2 = this.conversionRates.getSpecialMappingName(o2);
+ if (special1 != null || special2 != null) {
+ if (special1 == null) {
+ // non-specials come first
+ return -1;
+ }
+ if (special2 == null) {
+ // non-specials come first
+ return 1;
+ }
+ // both are specials, compare lexicographically
+ return special1.compareTo(special2);
+ }
BigDecimal factor1 = this.conversionRates.getFactorToBase(o1).getConversionRate();
BigDecimal factor2 = this.conversionRates.getFactorToBase(o2).getConversionRate();
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/SingleUnitImpl.java b/android_icu4j/src/main/java/android/icu/impl/units/SingleUnitImpl.java
index 3fc1b4e..02b19c7 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/SingleUnitImpl.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/SingleUnitImpl.java
@@ -30,7 +30,7 @@
*/
private String simpleUnitID = "";
/**
- * Determine the power of the `SingleUnit`. For example, for "square-meter", the dimensionality will be `2`.
+ * Determine the power of the {@code SingleUnit}. For example, for "square-meter", the dimensionality will be {@code 2}.
* <p>
* NOTE:
* Default dimensionality is 1.
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java
index fd15712..2c8a42d 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java
@@ -7,7 +7,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.Locale;
+import java.util.List;
import java.util.Map;
import android.icu.impl.ICUData;
@@ -96,36 +96,39 @@
}
}
- String region = locale.getCountry();
+ String region = ULocale.getRegionForSupplementalData(locale, true);
// Check the locale system tag, e.g `ms=metric`.
String localeSystem = locale.getKeywordValue("measure");
- boolean isLocaleSystem = false;
- if (measurementSystem.containsKey(localeSystem)) {
- isLocaleSystem = true;
- region = measurementSystem.get(localeSystem);
- }
-
- // Check the region tag, e.g. `rg=uszzz`.
- if (!isLocaleSystem) {
- String localeRegion = locale.getKeywordValue("rg");
- if (localeRegion != null && localeRegion.length() >= 3) {
- if (localeRegion.equals("default")) {
- region = localeRegion;
- } else if (Character.isDigit(localeRegion.charAt(0))) {
- region = localeRegion.substring(0, 3); // e.g. 001
- } else {
- // Capitalize the first two character of the region, e.g. ukzzzz or usca
- region = localeRegion.substring(0, 2).toUpperCase(Locale.ROOT);
- }
- }
- }
+ boolean isLocaleSystem = measurementSystem.containsKey(localeSystem);
String[] subUsages = getAllUsages(usage);
UnitPreference[] result = null;
for (String subUsage :
subUsages) {
result = getUnitPreferences(category, subUsage, region);
+
+ if (result != null && isLocaleSystem) {
+ ConversionRates rates = new ConversionRates();
+ boolean unitsMatchSystem = true;
+ for (UnitPreference unitPref : result) {
+ MeasureUnitImpl measureUnit = MeasureUnitImpl.forIdentifier(unitPref.getUnit());
+ List<SingleUnitImpl> singleUnits = new ArrayList<>(measureUnit.getSingleUnits());
+ for (SingleUnitImpl singleUnit : singleUnits) {
+ String systems = rates.extractSystems(singleUnit);
+ if (!systems.contains("metric_adjacent")) {
+ if (!systems.contains(localeSystem)) {
+ unitsMatchSystem = false;
+ }
+ }
+ }
+ }
+ if (!unitsMatchSystem) {
+ String newRegion = measurementSystem.get(localeSystem);
+ result = getUnitPreferences(category, subUsage, newRegion);
+ }
+ }
+
if (result != null) break;
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java
index 8aea577..d18e4e7 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java
@@ -7,12 +7,14 @@
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.regex.Pattern;
import android.icu.impl.IllegalIcuArgumentException;
import android.icu.util.MeasureUnit;
+// TODO ICU-22683: Consider splitting handling of special mappings into separate (possibly internal) class
/**
* @hide Only a subset of ICU is exposed in Android
*/
@@ -20,6 +22,8 @@
private BigDecimal conversionRate;
private boolean reciprocal;
private BigDecimal offset;
+ private String specialSource;
+ private String specialTarget;
/**
* Constructor of <code>UnitsConverter</code>.
@@ -46,6 +50,7 @@
* NOTE:
* - source and target must be under the same category
* - e.g. meter to mile --> both of them are length units.
+ * This converts from source to base to target (one of those may be a no-op).
*
* @param source represents the source unit.
* @param target represents the target unit.
@@ -57,21 +62,38 @@
throw new IllegalIcuArgumentException("input units must be convertible or reciprocal");
}
- Factor sourceToBase = conversionRates.getFactorToBase(source);
- Factor targetToBase = conversionRates.getFactorToBase(target);
+ this.specialSource = conversionRates.getSpecialMappingName(source);
+ this.specialTarget = conversionRates.getSpecialMappingName(target);
- if (convertibility == Convertibility.CONVERTIBLE) {
- this.conversionRate = sourceToBase.divide(targetToBase).getConversionRate();
+ if (this.specialSource == null && this.specialTarget == null) {
+ Factor sourceToBase = conversionRates.getFactorToBase(source);
+ Factor targetToBase = conversionRates.getFactorToBase(target);
+
+ if (convertibility == Convertibility.CONVERTIBLE) {
+ this.conversionRate = sourceToBase.divide(targetToBase).getConversionRate();
+ } else {
+ assert convertibility == Convertibility.RECIPROCAL;
+ this.conversionRate = sourceToBase.multiply(targetToBase).getConversionRate();
+ }
+ this.reciprocal = convertibility == Convertibility.RECIPROCAL;
+
+ // calculate the offset
+ this.offset = conversionRates.getOffset(source, target, sourceToBase, targetToBase, convertibility);
+ // We should see no offsets for reciprocal conversions - they don't make sense:
+ assert convertibility != Convertibility.RECIPROCAL || this.offset == BigDecimal.ZERO;
} else {
- assert convertibility == Convertibility.RECIPROCAL;
- this.conversionRate = sourceToBase.multiply(targetToBase).getConversionRate();
+ this.reciprocal = false;
+ this.offset = BigDecimal.ZERO;
+ if (this.specialSource == null) {
+ // conversionRate is for source to base only
+ this.conversionRate = conversionRates.getFactorToBase(source).getConversionRate();
+ } else if (this.specialTarget == null) {
+ // conversionRate is for base to target only
+ this.conversionRate = conversionRates.getFactorToBase(target).getConversionRate();
+ } else {
+ this.conversionRate = BigDecimal.ONE;
+ }
}
- this.reciprocal = convertibility == Convertibility.RECIPROCAL;
-
- // calculate the offset
- this.offset = conversionRates.getOffset(source, target, sourceToBase, targetToBase, convertibility);
- // We should see no offsets for reciprocal conversions - they don't make sense:
- assert convertibility != Convertibility.RECIPROCAL || this.offset == BigDecimal.ZERO;
}
static public Convertibility extractConvertibility(MeasureUnitImpl source, MeasureUnitImpl target, ConversionRates conversionRates) {
@@ -114,8 +136,34 @@
return true;
}
+ // Convert inputValue (source) to base then to target
public BigDecimal convert(BigDecimal inputValue) {
- BigDecimal result = inputValue.multiply(this.conversionRate).add(offset);
+ BigDecimal result = inputValue;
+ if (this.specialSource != null || this.specialTarget != null) {
+ BigDecimal base = inputValue;
+ // convert input (=source) to base
+ if (this.specialSource != null) {
+ // We have a special mapping from source to base (not using factor, offset).
+ // Currently the only supported mapping is a scale-based mapping for beaufort.
+ base = (this.specialSource.equals("beaufort"))?
+ scaleToBase(inputValue, minMetersPerSecForBeaufort): inputValue;
+ } else {
+ // Standard mapping (using factor, offset) from source to base.
+ base = inputValue.multiply(this.conversionRate);
+ }
+ // convert base to result (=target)
+ if (this.specialTarget != null) {
+ // We have a special mapping from base to target (not using factor, offset).
+ // Currently the only supported mapping is a scale-based mapping for beaufort.
+ result = (this.specialTarget.equals("beaufort"))?
+ baseToScale(base, minMetersPerSecForBeaufort): base;
+ } else {
+ // Standard mapping (using factor, offset) from base to target.
+ result = base.divide(this.conversionRate, DECIMAL128);
+ }
+ return result;
+ }
+ result = inputValue.multiply(this.conversionRate).add(offset);
if (this.reciprocal) {
// We should see no offsets for reciprocal conversions - they don't make sense:
assert offset == BigDecimal.ZERO;
@@ -128,8 +176,33 @@
return result;
}
+ // Convert inputValue (target) to base then to source
public BigDecimal convertInverse(BigDecimal inputValue) {
BigDecimal result = inputValue;
+ if (this.specialSource != null || this.specialTarget != null) {
+ BigDecimal base = inputValue;
+ // convert input (=target) to base
+ if (this.specialTarget != null) {
+ // We have a special mapping from target to base (not using factor, offset).
+ // Currently the only supported mapping is a scale-based mapping for beaufort.
+ base = (this.specialTarget.equals("beaufort"))?
+ scaleToBase(inputValue, minMetersPerSecForBeaufort): inputValue;
+ } else {
+ // Standard mapping (using factor, offset) from target to base.
+ base = inputValue.multiply(this.conversionRate);
+ }
+ // convert base to result (=source)
+ if (this.specialSource != null) {
+ // We have a special mapping from base to source (not using factor, offset).
+ // Currently the only supported mapping is a scale-based mapping for beaufort.
+ result = (this.specialSource.equals("beaufort"))?
+ baseToScale(base, minMetersPerSecForBeaufort): base;
+ } else {
+ // Standard mapping (using factor, offset) from base to source.
+ result = base.divide(this.conversionRate, DECIMAL128);
+ }
+ return result;
+ }
if (this.reciprocal) {
// We should see no offsets for reciprocal conversions - they don't make sense:
assert offset == BigDecimal.ZERO;
@@ -143,6 +216,64 @@
return result;
}
+ // TODO per CLDR-17421 and ICU-22683: consider getting the data below from CLDR
+ private static final BigDecimal[] minMetersPerSecForBeaufort = {
+ // Minimum m/s (base) values for each Bft value, plus an extra artificial value;
+ // when converting from Bft to m/s, the middle of the range will be used
+ // (Values from table in Wikipedia, except for artificial value).
+ // Since this is 0 based, max Beaufort value is thus array dimension minus 2.
+ BigDecimal.valueOf(0.0), // 0 Bft
+ BigDecimal.valueOf(0.3), // 1
+ BigDecimal.valueOf(1.6), // 2
+ BigDecimal.valueOf(3.4), // 3
+ BigDecimal.valueOf(5.5), // 4
+ BigDecimal.valueOf(8.0), // 5
+ BigDecimal.valueOf(10.8), // 6
+ BigDecimal.valueOf(13.9), // 7
+ BigDecimal.valueOf(17.2), // 8
+ BigDecimal.valueOf(20.8), // 9
+ BigDecimal.valueOf(24.5), // 10
+ BigDecimal.valueOf(28.5), // 11
+ BigDecimal.valueOf(32.7), // 12
+ BigDecimal.valueOf(36.9), // 13
+ BigDecimal.valueOf(41.4), // 14
+ BigDecimal.valueOf(46.1), // 15
+ BigDecimal.valueOf(51.1), // 16
+ BigDecimal.valueOf(55.8), // 17
+ BigDecimal.valueOf(61.4), // artificial end of range 17 to give reasonable midpoint
+ };
+
+ // Convert from what should be discrete scale values for a particular unit like beaufort
+ // to a corresponding value in the base unit (which can have any decimal value, like meters/sec).
+ // First we round the scale value to the nearest integer (in case it is specified with a fractional value),
+ // then we map that to a value in middle of the range of corresponding base values.
+ // This can handle different scales, specified by minBaseForScaleValues[].
+ private BigDecimal scaleToBase(BigDecimal scaleValue, BigDecimal[] minBaseForScaleValues) {
+ BigDecimal pointFive = BigDecimal.valueOf(0.5);
+ BigDecimal scaleAdjust = scaleValue.abs().add(pointFive); // adjust up for later truncation
+ BigDecimal scaleAdjustCapped = scaleAdjust.min(BigDecimal.valueOf(minBaseForScaleValues.length - 2));
+ int scaleIndex = scaleAdjustCapped.intValue();
+ // Return midpont of range (the final range uses an articial end to produce reasonable midpoint)
+ return minBaseForScaleValues[scaleIndex].add(minBaseForScaleValues[scaleIndex + 1]).multiply(pointFive);
+ }
+
+ // Convert from a value in the base unit (which can have any decimal value, like meters/sec) to a corresponding
+ // discrete value in a scale (like beaufort), where each scale value represents a range of base values.
+ // We binary-search the ranges to find the one that contains the specified base value, and return its index.
+ // This can handle different scales, specified by minBaseForScaleValues[].
+ private BigDecimal baseToScale(BigDecimal baseValue, BigDecimal[] minBaseForScaleValues) {
+ int scaleIndex = Arrays.binarySearch(minBaseForScaleValues, baseValue.abs());
+ if (scaleIndex < 0) {
+ // since our first array entry is 0, this value will always be -2 or less
+ scaleIndex = -scaleIndex - 2;
+ }
+ int scaleMax = minBaseForScaleValues.length - 2;
+ if (scaleIndex > scaleMax) {
+ scaleIndex = scaleMax;
+ }
+ return BigDecimal.valueOf(scaleIndex);
+ }
+
/**
* @hide Only a subset of ICU is exposed in Android
*/
@@ -203,6 +334,14 @@
private int exponentSecPerJulianYear = 0;
/** Exponent for the speed of light meters per second" conversion rate constant */
private int exponentSpeedOfLightMetersPerSecond = 0;
+ /** Exponent for https://en.wikipedia.org/wiki/Japanese_units_of_measurement */
+ private int exponentShoToM3 = 0;
+ /** Exponent for https://en.wikipedia.org/wiki/Japanese_units_of_measurement */
+ private int exponentTsuboToM2 = 0;
+ /** Exponent for https://en.wikipedia.org/wiki/Japanese_units_of_measurement */
+ private int exponentShakuToM = 0;
+ /** Exponent for Atomic Mass Unit */
+ private int exponentAMU = 0;
/**
* Creates Empty Factor
@@ -259,12 +398,16 @@
result.exponentMetersPerAU = this.exponentMetersPerAU;
result.exponentSecPerJulianYear = this.exponentSecPerJulianYear;
result.exponentSpeedOfLightMetersPerSecond = this.exponentSpeedOfLightMetersPerSecond;
+ result.exponentShoToM3 = this.exponentShoToM3;
+ result.exponentTsuboToM2 = this.exponentTsuboToM2;
+ result.exponentShakuToM = this.exponentShakuToM;
+ result.exponentAMU = this.exponentAMU;
return result;
}
/**
- * Returns a single `BigDecimal` that represent the conversion rate after substituting all the constants.
+ * Returns a single {@code BigDecimal} that represent the conversion rate after substituting all the constants.
*
* In ICU4C, see Factor::substituteConstants().
*/
@@ -289,6 +432,10 @@
resultCollector.multiply(new BigDecimal("149597870700"), this.exponentMetersPerAU);
resultCollector.multiply(new BigDecimal("31557600"), this.exponentSecPerJulianYear);
resultCollector.multiply(new BigDecimal("299792458"), this.exponentSpeedOfLightMetersPerSecond);
+ resultCollector.multiply(new BigDecimal("0.001803906836964688204"), this.exponentShoToM3); // 2401/(1331*1000)
+ resultCollector.multiply(new BigDecimal("3.305785123966942"), this.exponentTsuboToM2); // 400/121
+ resultCollector.multiply(new BigDecimal("0.033057851239669"), this.exponentShakuToM); // 4/121
+ resultCollector.multiply(new BigDecimal("1.66053878283E-27"), this.exponentAMU);
return resultCollector.factorNum.divide(resultCollector.factorDen, DECIMAL128);
}
@@ -349,6 +496,10 @@
result.exponentSecPerJulianYear = this.exponentSecPerJulianYear * power;
result.exponentSpeedOfLightMetersPerSecond =
this.exponentSpeedOfLightMetersPerSecond * power;
+ result.exponentShoToM3 = this.exponentShoToM3 * power;
+ result.exponentTsuboToM2 = this.exponentTsuboToM2 * power;
+ result.exponentShakuToM = this.exponentShakuToM * power;
+ result.exponentAMU = this.exponentAMU * power;
return result;
}
@@ -371,6 +522,10 @@
result.exponentSecPerJulianYear = this.exponentSecPerJulianYear - other.exponentSecPerJulianYear;
result.exponentSpeedOfLightMetersPerSecond =
this.exponentSpeedOfLightMetersPerSecond - other.exponentSpeedOfLightMetersPerSecond;
+ result.exponentShoToM3 = this.exponentShoToM3 - other.exponentShoToM3;
+ result.exponentTsuboToM2 = this.exponentTsuboToM2 - other.exponentTsuboToM2;
+ result.exponentShakuToM = this.exponentShakuToM - other.exponentShakuToM;
+ result.exponentAMU = this.exponentAMU - other.exponentAMU;
return result;
}
@@ -393,12 +548,16 @@
result.exponentSecPerJulianYear = this.exponentSecPerJulianYear + other.exponentSecPerJulianYear;
result.exponentSpeedOfLightMetersPerSecond =
this.exponentSpeedOfLightMetersPerSecond + other.exponentSpeedOfLightMetersPerSecond;
+ result.exponentShoToM3 = this.exponentShoToM3 + other.exponentShoToM3;
+ result.exponentTsuboToM2 = this.exponentTsuboToM2 + other.exponentTsuboToM2;
+ result.exponentShakuToM = this.exponentShakuToM + other.exponentShakuToM;
+ result.exponentAMU = this.exponentAMU + other.exponentAMU;
return result;
}
/**
- * Adds Entity with power or not. For example, `12 ^ 3` or `12`.
+ * Adds Entity with power or not. For example, {@code 12 ^ 3} or {@code 12}.
*
* @param poweredEntity
*/
@@ -440,11 +599,19 @@
this.exponentMetersPerAU += power;
} else if ("PI".equals(entity)) {
this.exponentPi += power;
- } else if ("sec_per_julian_year".equals(entity)) {
+ } else if ("sec_per_julian_year".equals(entity)) {
this.exponentSecPerJulianYear += power;
} else if ("speed_of_light_meters_per_second".equals(entity)) {
this.exponentSpeedOfLightMetersPerSecond += power;
- } else {
+ } else if ("sho_to_m3".equals(entity)) {
+ this.exponentShoToM3 += power;
+ } else if ("tsubo_to_m2".equals(entity)) {
+ this.exponentTsuboToM2 += power;
+ } else if ("shaku_to_m".equals(entity)) {
+ this.exponentShakuToM += power;
+ } else if ("AMU".equals(entity)) {
+ this.exponentAMU += power;
+ } else {
BigDecimal decimalEntity = new BigDecimal(entity).pow(power, DECIMAL128);
this.factorNum = this.factorNum.multiply(decimalEntity);
}
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitsData.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitsData.java
index a57bc55..3a416c6 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitsData.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitsData.java
@@ -230,10 +230,10 @@
}
/**
- * A Resource Sink that collects information from `unitQuantities` in the
- * `units` resource to provide key->value lookups from base unit to
+ * A Resource Sink that collects information from {@code unitQuantities} in the
+ * {@code units} resource to provide key->value lookups from base unit to
* category, as well as preserving ordering information for these
- * categories. See `units.txt`.
+ * categories. See {@code units.txt}.
*
* For example: "kilogram" -> "mass", "meter-per-second" -> "speed".
*
diff --git a/android_icu4j/src/main/java/android/icu/impl/units/UnitsRouter.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitsRouter.java
index 383642e..8d349bb 100644
--- a/android_icu4j/src/main/java/android/icu/impl/units/UnitsRouter.java
+++ b/android_icu4j/src/main/java/android/icu/impl/units/UnitsRouter.java
@@ -14,32 +14,32 @@
import android.icu.util.ULocale;
/**
- * `UnitsRouter` responsible for converting from a single unit (such as `meter` or `meter-per-second`) to
- * one of the complex units based on the limits.
+ * {@code UnitsRouter} responsible for converting from a single unit (such as {@code meter} or
+ * {@code meter-per-second}) to one of the complex units based on the limits.
* For example:
- * if the input is `meter` and the output as following
- * {`foot+inch`, limit: 3.0}
- * {`inch` , limit: no value (-inf)}
- * Thus means if the input in `meter` is greater than or equal to `3.0 feet`, the output will be in
- * `foot+inch`, otherwise, the output will be in `inch`.
+ * if the input is {@code meter} and the output as following
+ * {{@code foot+inch}, limit: 3.0}
+ * {{@code inch} , limit: no value (-inf)}
+ * Thus means if the input in {@code meter} is greater than or equal to {@code 3.0 feet},
+ * the output will be in {@code foot+inch}, otherwise, the output will be in {@code inch}.
* <p>
* NOTE:
* the output units and their limits MUST BE in order, for example, if the output units, from the
* previous example, are the following:
- * {`inch` , limit: no value (-inf)}
- * {`foot+inch`, limit: 3.0}
- * IN THIS CASE THE OUTPUT WILL BE ALWAYS IN `inch`.
+ * {{@code inch} , limit: no value (-inf)}
+ * {{@code foot+inch}, limit: 3.0}
+ * IN THIS CASE THE OUTPUT WILL BE ALWAYS IN {@code inch}.
* <p>
* NOTE:
- * the output units and their limits will be extracted from the units preferences database by knowing
+ * the output units and their limits will be extracted from the units preferences database by knowing
* the followings:
* - input unit
* - locale
* - usage
* <p>
* DESIGN:
- * `UnitRouter` uses internally `ComplexUnitConverter` in order to convert the input units to the
- * desired complex units and to check the limit too.
+ * {@code UnitRouter} uses internally {@code ComplexUnitConverter} in order to convert the input
+ * units to the desired complex units and to check the limit too.
* @hide Only a subset of ICU is exposed in Android
*/
public class UnitsRouter {
@@ -148,12 +148,12 @@
/**
* Contains the complex unit converter and the limit which representing the smallest value that the
- * converter should accept. For example, if the converter is converting to `foot+inch` and the limit
- * equals 3.0, thus means the converter should not convert to a value less than `3.0 feet`.
+ * converter should accept. For example, if the converter is converting to {@code foot+inch} and the
+ * limit equals 3.0, thus means the converter should not convert to a value less than {@code 3.0 feet}.
* <p>
* NOTE:
- * if the limit doest not has a value `i.e. (std::numeric_limits<double>::lowest())`, this mean there
- * is no limit for the converter.
+ * if the limit doest not has a value (i.e. {@code std::numeric_limits<double>::lowest()}),
+ * this mean there is no limit for the converter.
* @hide Only a subset of ICU is exposed in Android
*/
public static class ConverterPreference {
diff --git a/android_icu4j/src/main/java/android/icu/lang/CharSequences.java b/android_icu4j/src/main/java/android/icu/lang/CharSequences.java
index 6ec0048..dee800f 100644
--- a/android_icu4j/src/main/java/android/icu/lang/CharSequences.java
+++ b/android_icu4j/src/main/java/android/icu/lang/CharSequences.java
@@ -125,7 +125,7 @@
* Same results as turning the code point into a string (with the [ugly] new StringBuilder().appendCodePoint(codepoint).toString())
* and comparing, but much faster (no object creation).
* Actually, there is one difference; a null compares as less.
- * Note that this (=String) order is UTF-16 order -- *not* code point order.
+ * Note that this (=String) order is UTF-16 order -- <i>not</i> code point order.
*
* @deprecated This API is ICU internal only.
* @hide draft / provisional / internal are hidden on Android
@@ -169,7 +169,7 @@
* Utility to compare a string to a code point.
* Same results as turning the code point into a string and comparing, but much faster (no object creation).
* Actually, there is one difference; a null compares as less.
- * Note that this (=String) order is UTF-16 order -- *not* code point order.
+ * Note that this (=String) order is UTF-16 order -- <i>not</i> code point order.
*
* @deprecated This API is ICU internal only.
* @hide draft / provisional / internal are hidden on Android
diff --git a/android_icu4j/src/main/java/android/icu/lang/UCharacter.java b/android_icu4j/src/main/java/android/icu/lang/UCharacter.java
index 8dc245a..b25205f 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UCharacter.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UCharacter.java
@@ -11,6 +11,7 @@
package android.icu.lang;
import java.lang.ref.SoftReference;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
@@ -1132,7 +1133,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int COUNT = 328;
+ public static final int COUNT = 329;
// blocks objects ---------------------------------------------------
@@ -2519,9 +2520,7 @@
{
super(name);
m_id_ = id;
- // Android-changed: Avoid leaking flagged UnicodeBlock until ICU 74 data is integrated.
- // if (id >= 0) {
- if (id >= 0 && id < BLOCKS_.length) {
+ if (id >= 0) {
BLOCKS_[id] = this;
}
}
@@ -3314,7 +3313,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int COUNT = 43;
+ public static final int COUNT = 48;
}
/**
@@ -3547,6 +3546,56 @@
public static final int UPRIGHT = 3;
}
+ /**
+ * Identifier Status constants.
+ * See https://www.unicode.org/reports/tr39/#Identifier_Status_and_Type.
+ *
+ * @see UProperty#IDENTIFIER_STATUS
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum IdentifierStatus {
+ /** @hide draft / provisional / internal are hidden on Android*/
+ RESTRICTED,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ ALLOWED,
+ }
+
+ /**
+ * Identifier Type constants.
+ * See https://www.unicode.org/reports/tr39/#Identifier_Status_and_Type.
+ *
+ * @see UProperty#IDENTIFIER_TYPE
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public enum IdentifierType {
+ /** @hide draft / provisional / internal are hidden on Android*/
+ NOT_CHARACTER,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ DEPRECATED,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ DEFAULT_IGNORABLE,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ NOT_NFKC,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ NOT_XID,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ EXCLUSION,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ OBSOLETE,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ TECHNICAL,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ UNCOMMON_USE,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ LIMITED_USE,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ INCLUSION,
+ /** @hide draft / provisional / internal are hidden on Android*/
+ RECOMMENDED,
+ }
+
// public data members -----------------------------------------------
/**
@@ -3981,79 +4030,79 @@
}
/**
- * Determines if the specified code point may be any part of a Unicode
- * identifier other than the starting character.
- * A code point may be part of a Unicode identifier if and only if it is
- * one of the following:
- * <ul>
- * <li> Lu Uppercase letter
- * <li> Ll Lowercase letter
- * <li> Lt Titlecase letter
- * <li> Lm Modifier letter
- * <li> Lo Other letter
- * <li> Nl Letter number
- * <li> Pc Connecting punctuation character
- * <li> Nd decimal number
- * <li> Mc Spacing combining mark
- * <li> Mn Non-spacing mark
- * <li> Cf formatting code
- * </ul>
- * Up-to-date Unicode implementation of
- * java.lang.Character.isUnicodeIdentifierPart().<br>
- * See <a href=https://www.unicode.org/reports/tr8/>UTR #8</a>.
- * @param ch code point to determine if is can be part of a Unicode
- * identifier
- * @return true if code point is any character belonging a unicode
- * identifier suffix after the first character
+ * Determines if the specified character is permissible as a
+ * non-initial character of an identifier
+ * according to UAX #31 Unicode Identifier and Pattern Syntax.
+ *
+ * <p>Same as Unicode ID_Continue ({@link UProperty#ID_CONTINUE}).
+ *
+ * <p>Note that this differs from {@link java.lang.Character#isUnicodeIdentifierPart(char)}
+ * which implements a different identifier profile.
+ *
+ * @param ch the code point to be tested
+ * @return true if the code point may occur as a non-initial character of an identifier
*/
public static boolean isUnicodeIdentifierPart(int ch)
{
- // if props == 0, it will just fall through and return false
- // cat == format
- return ((1 << getType(ch))
- & ((1 << UCharacterCategory.UPPERCASE_LETTER)
- | (1 << UCharacterCategory.LOWERCASE_LETTER)
- | (1 << UCharacterCategory.TITLECASE_LETTER)
- | (1 << UCharacterCategory.MODIFIER_LETTER)
- | (1 << UCharacterCategory.OTHER_LETTER)
- | (1 << UCharacterCategory.LETTER_NUMBER)
- | (1 << UCharacterCategory.CONNECTOR_PUNCTUATION)
- | (1 << UCharacterCategory.DECIMAL_DIGIT_NUMBER)
- | (1 << UCharacterCategory.COMBINING_SPACING_MARK)
- | (1 << UCharacterCategory.NON_SPACING_MARK))) != 0
- || isIdentifierIgnorable(ch);
+ return hasBinaryProperty(ch, UProperty.ID_CONTINUE); // single code point
}
/**
- * Determines if the specified code point is permissible as the first
- * character in a Unicode identifier.
- * A code point may start a Unicode identifier if it is of type either
- * <ul>
- * <li> Lu Uppercase letter
- * <li> Ll Lowercase letter
- * <li> Lt Titlecase letter
- * <li> Lm Modifier letter
- * <li> Lo Other letter
- * <li> Nl Letter number
- * </ul>
- * Up-to-date Unicode implementation of
- * java.lang.Character.isUnicodeIdentifierStart().<br>
- * See <a href=https://www.unicode.org/reports/tr8/>UTR #8</a>.
- * @param ch code point to determine if it can start a Unicode identifier
- * @return true if code point is the first character belonging a unicode
- * identifier
+ * Determines if the specified character is permissible as the first character in an identifier
+ * according to UAX #31 Unicode Identifier and Pattern Syntax.
+ *
+ * <p>Same as Unicode ID_Start ({@link UProperty#ID_START}).
+ *
+ * <p>Note that this differs from {@link java.lang.Character#isUnicodeIdentifierStart(char)}
+ * which implements a different identifier profile.
+ *
+ * @param ch the code point to be tested
+ * @return true if the code point may start an identifier
*/
public static boolean isUnicodeIdentifierStart(int ch)
{
- /*int cat = getType(ch);*/
- // if props == 0, it will just fall through and return false
- return ((1 << getType(ch))
- & ((1 << UCharacterCategory.UPPERCASE_LETTER)
- | (1 << UCharacterCategory.LOWERCASE_LETTER)
- | (1 << UCharacterCategory.TITLECASE_LETTER)
- | (1 << UCharacterCategory.MODIFIER_LETTER)
- | (1 << UCharacterCategory.OTHER_LETTER)
- | (1 << UCharacterCategory.LETTER_NUMBER))) != 0;
+ return hasBinaryProperty(ch, UProperty.ID_START); // single code point
+ }
+
+ /**
+ * Does the set of Identifier_Type values code point c contain the given type?
+ *
+ * <p>Used for UTS #39 General Security Profile for Identifiers
+ * (https://www.unicode.org/reports/tr39/#General_Security_Profile).
+ *
+ * <p>Each code point maps to a <i>set</i> of UIdentifierType values.
+ *
+ * @param c code point
+ * @param type Identifier_Type to check
+ * @return true if type is in Identifier_Type(c)
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final boolean hasIdentifierType(int c, IdentifierType type) {
+ return UCharacterProperty.INSTANCE.hasIDType(c, type);
+ }
+
+ /**
+ * Writes code point c's Identifier_Type as a set of IdentifierType values and
+ * returns the number of types.
+ * The set is cleared before c's types are added.
+ *
+ * <p>Used for UTS #39 General Security Profile for Identifiers
+ * (https://www.unicode.org/reports/tr39/#General_Security_Profile).
+ *
+ * <p>Each code point maps to a <i>set</i> of IdentifierType values.
+ * There is always at least one type.
+ * Only some of the types can be combined with others,
+ * and usually only a small number of types occur together.
+ * Future versions might add additional types.
+ * See UTS #39 and its data files for details.
+ *
+ * @param c code point
+ * @param types output set
+ * @return number of values in c's Identifier_Type
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final int getIdentifierTypes(int c, EnumSet<IdentifierType> types) {
+ return UCharacterProperty.INSTANCE.getIDTypes(c, types);
}
/**
@@ -4404,9 +4453,10 @@
/**
* <strong>[icu]</strong> Returns the most current Unicode name of the argument code point, or
* null if the character is unassigned or outside the range
- * UCharacter.MIN_VALUE and UCharacter.MAX_VALUE or does not have a name.
+ * {@code UCharacter.MIN_VALUE} and {@code UCharacter.MAX_VALUE} or does not
+ * have a name.
* <br>
- * Note calling any methods related to code point names, e.g. get*Name*()
+ * Note calling any methods related to code point names, e.g. {@code getName()}
* incurs a one-time initialization cost to construct the name tables.
* @param ch the code point for which to get the name
* @return most current Unicode name
@@ -4462,7 +4512,7 @@
* <li> Extended name in the form of
* "<codepoint_type-codepoint_hex_digits>". E.g., <noncharacter-fffe>
* </ul>
- * Note calling any methods related to code point names, e.g. get*Name*()
+ * Note calling any methods related to code point names, e.g. {@code getName()}
* incurs a one-time initialization cost to construct the name tables.
* @param ch the code point for which to get the name
* @return a name for the argument codepoint
@@ -4476,7 +4526,7 @@
* Returns null if the character is unassigned or outside the range
* UCharacter.MIN_VALUE and UCharacter.MAX_VALUE or does not have a name.
* <br>
- * Note calling any methods related to code point names, e.g. get*Name*()
+ * Note calling any methods related to code point names, e.g. {@code getName()}
* incurs a one-time initialization cost to construct the name tables.
* @param ch the code point for which to get the name alias
* @return Unicode name alias, or null
@@ -4506,7 +4556,7 @@
/**
* <strong>[icu]</strong> <p>Finds a Unicode code point by its most current Unicode name and
* return its code point value. All Unicode names are in uppercase.
- * Note calling any methods related to code point names, e.g. get*Name*()
+ * Note calling any methods related to code point names, e.g. {@code getName()}
* incurs a one-time initialization cost to construct the name tables.
* @param name most current Unicode character name whose code point is to
* be returned
@@ -4545,7 +4595,7 @@
* <li> Extended name in the form of
* "<codepoint_type-codepoint_hex_digits>". E.g. <noncharacter-FFFE>
* </ul>
- * Note calling any methods related to code point names, e.g. get*Name*()
+ * Note calling any methods related to code point names, e.g. {@code getName()}
* incurs a one-time initialization cost to construct the name tables.
* @param name codepoint name
* @return code point associated with the name or -1 if the name is not
@@ -4559,7 +4609,7 @@
/**
* <strong>[icu]</strong> <p>Find a Unicode character by its corrected name alias and return
* its code point value. All Unicode names are in uppercase.
- * Note calling any methods related to code point names, e.g. get*Name*()
+ * Note calling any methods related to code point names, e.g. {@code getName()}
* incurs a one-time initialization cost to construct the name tables.
* @param name Unicode name alias whose code point is to be returned
* @return code point or -1 if name is not found
diff --git a/android_icu4j/src/main/java/android/icu/lang/UProperty.java b/android_icu4j/src/main/java/android/icu/lang/UProperty.java
index f0057c3..0011881 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UProperty.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UProperty.java
@@ -280,7 +280,7 @@
/**
* <p>Binary property Case_Sensitive.
* <p>Either the source of a case
- * mapping or _in_ the target of a case mapping. Not the same as
+ * mapping or <i>in</i> the target of a case mapping. Not the same as
* the general category Cased_Letter.
*/
public static final int CASE_SENSITIVE = 34;
@@ -529,12 +529,36 @@
public static final int RGI_EMOJI=71;
/**
+ * Binary property IDS_Unary_Operator.
+ * For programmatic determination of Ideographic Description Sequences.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final int IDS_UNARY_OPERATOR = 72;
+
+ /**
+ * Binary property ID_Compat_Math_Start.
+ * <p>Used in mathematical identifier profile in UAX #31.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final int ID_COMPAT_MATH_START = 73;
+
+ /**
+ * Binary property ID_Compat_Math_Continue.
+ * <p>Used in mathematical identifier profile in UAX #31.
+ *
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final int ID_COMPAT_MATH_CONTINUE = 74;
+
+ /**
* One more than the last constant for binary Unicode properties.
* @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420.
* @hide unsupported on Android
*/
@Deprecated
- public static final int BINARY_LIMIT = 72;
+ public static final int BINARY_LIMIT = 75;
/**
* Enumerated property Bidi_Class.
@@ -714,12 +738,20 @@
public static final int VERTICAL_ORIENTATION = 0x1018;
/**
+ * Enumerated property Identifier_Status.
+ * Used for UTS #39 General Security Profile for Identifiers
+ * (https://www.unicode.org/reports/tr39/#General_Security_Profile).
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final int IDENTIFIER_STATUS = 0x1019;
+
+ /**
* One more than the last constant for enumerated/integer Unicode properties.
* @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420.
* @hide unsupported on Android
*/
@Deprecated
- public static final int INT_LIMIT = 0x1019;
+ public static final int INT_LIMIT = 0x101A;
/**
* Bitmask property General_Category_Mask.
@@ -880,12 +912,27 @@
*/
public static final int OTHER_PROPERTY_START=SCRIPT_EXTENSIONS;
/**
+ * Miscellaneous property Identifier_Type.
+ * Used for UTS #39 General Security Profile for Identifiers
+ * (https://www.unicode.org/reports/tr39/#General_Security_Profile).
+ *
+ * <p>Corresponds to {@link UCharacter#hasIdentifierType(int, UCharacter.IdentifierType)} and
+ * {@link UCharacter#getIdentifierTypes(int, java.util.EnumSet)}.
+ *
+ * <p>Each code point maps to a <i>set</i> of IdentifierType values.
+ *
+ * @see UCharacter#hasIdentifierType(int, UCharacter.IdentifierType)
+ * @see UCharacter#getIdentifierTypes(int, java.util.EnumSet)
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ public static final int IDENTIFIER_TYPE = 0x7001;
+ /**
* One more than the last constant for Unicode properties with unusual value types.
* @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420.
* @hide unsupported on Android
*/
@Deprecated
- public static final int OTHER_PROPERTY_LIMIT=0x7001;
+ public static final int OTHER_PROPERTY_LIMIT = 0x7002;
/**
* Selector constants for UCharacter.getPropertyName() and
diff --git a/android_icu4j/src/main/java/android/icu/lang/UScript.java b/android_icu4j/src/main/java/android/icu/lang/UScript.java
index 1a98ba2..f4972e5 100644
--- a/android_icu4j/src/main/java/android/icu/lang/UScript.java
+++ b/android_icu4j/src/main/java/android/icu/lang/UScript.java
@@ -880,6 +880,9 @@
/***/
public static final int NAG_MUNDARI = 199; /* Nagm */
+ /** @hide unsupported on Android*/
+ public static final int ARABIC_NASTALIQ = 200; /* Aran */
+
/**
* One more than the highest normal UScript code.
* The highest value is available via UCharacter.getIntPropertyMaxValue(UProperty.SCRIPT).
@@ -888,7 +891,7 @@
* @hide unsupported on Android
*/
@Deprecated
- public static final int CODE_LIMIT = 200;
+ public static final int CODE_LIMIT = 201;
private static int[] getCodesFromLocale(ULocale locale) {
// Multi-script languages, equivalent to the LocaleScript data
@@ -964,7 +967,20 @@
*/
public static final int[] getCode(String nameOrAbbrOrLocale) {
boolean triedCode = false;
- if (nameOrAbbrOrLocale.indexOf('_') < 0 && nameOrAbbrOrLocale.indexOf('-') < 0) {
+ int lastSepPos = nameOrAbbrOrLocale.indexOf('_');
+ if (lastSepPos < 0) {
+ lastSepPos = nameOrAbbrOrLocale.indexOf('-');
+ }
+ // Favor interpretation of nameOrAbbrOrLocale as a script alias if either
+ // 1. nameOrAbbrOrLocale does not contain -/_. Handles Han, Mro, Nko, etc.
+ // 2. The last instance of -/_ is at offset 3, and the portion after that is
+ // longer than 4 characters (i.e. not a script or region code). This handles
+ // Old_Hungarian, Old_Italic, etc. ("old" is a valid language code)
+ // 3. The last instance of -/_ is at offset 7, and the portion after that is
+ // 3 characters. This handles New_Tai_Lue ("new" is a valid language code).
+ if ( lastSepPos < 0
+ || (lastSepPos == 3 && nameOrAbbrOrLocale.length() > 8)
+ || (lastSepPos == 7 && nameOrAbbrOrLocale.length() == 11) ) {
int propNum = UCharacter.getPropertyValueEnumNoThrow(UProperty.SCRIPT, nameOrAbbrOrLocale);
if (propNum != UProperty.UNDEFINED) {
return new int[] {propNum};
diff --git a/android_icu4j/src/main/java/android/icu/math/BigDecimal.java b/android_icu4j/src/main/java/android/icu/math/BigDecimal.java
index 9909e66..099fbdd 100644
--- a/android_icu4j/src/main/java/android/icu/math/BigDecimal.java
+++ b/android_icu4j/src/main/java/android/icu/math/BigDecimal.java
@@ -2814,7 +2814,7 @@
// Reminder: a zero double returns '0.0', so we cannot fastpath to
// use the constant ZERO. This might be important enough to justify
// a factory approach, a cache, or a few private constants, later.
- return new android.icu.math.BigDecimal((new java.lang.Double(dub)).toString());
+ return new android.icu.math.BigDecimal(Double.toString(dub));
}
/**
diff --git a/android_icu4j/src/main/java/android/icu/message2/DateTimeFormatterFactory.java b/android_icu4j/src/main/java/android/icu/message2/DateTimeFormatterFactory.java
index e01f72c..6b969b9 100644
--- a/android_icu4j/src/main/java/android/icu/message2/DateTimeFormatterFactory.java
+++ b/android_icu4j/src/main/java/android/icu/message2/DateTimeFormatterFactory.java
@@ -1,32 +1,52 @@
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2022 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
+// License & terms of use: https://www.unicode.org/copyright.html
package android.icu.message2;
+import java.util.Calendar;
import java.util.Locale;
import java.util.Map;
-import java.util.Objects;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
-import android.icu.impl.locale.AsciiUtil;
import android.icu.text.DateFormat;
-import android.icu.text.SimpleDateFormat;
/**
* Creates a {@link Formatter} doing formatting of date / time, similar to
* <code>{exp, date}</code> and <code>{exp, time}</code> in {@link android.icu.text.MessageFormat}.
*/
class DateTimeFormatterFactory implements FormatterFactory {
+ private final String kind;
+
+ // "datetime", "date", "time"
+ DateTimeFormatterFactory(String kind) {
+ switch (kind) {
+ case "date":
+ break;
+ case "time":
+ break;
+ case "datetime":
+ break;
+ default:
+ kind = "datetime";
+ }
+ this.kind = kind;
+ }
private static int stringToStyle(String option) {
- switch (AsciiUtil.toUpperString(option)) {
- case "FULL": return DateFormat.FULL;
- case "LONG": return DateFormat.LONG;
- case "MEDIUM": return DateFormat.MEDIUM;
- case "SHORT": return DateFormat.SHORT;
- case "": // intentional fall-through
- case "DEFAULT": return DateFormat.DEFAULT;
- default: throw new IllegalArgumentException("Invalid datetime style: " + option);
+ switch (option) {
+ case "full":
+ return DateFormat.FULL;
+ case "long":
+ return DateFormat.LONG;
+ case "medium":
+ return DateFormat.MEDIUM;
+ case "short":
+ return DateFormat.SHORT;
+ default:
+ throw new IllegalArgumentException("Invalid datetime style: " + option);
}
}
@@ -38,49 +58,279 @@
*/
@Override
public Formatter createFormatter(Locale locale, Map<String, Object> fixedOptions) {
- DateFormat df;
+ int dateStyle = DateFormat.NONE;
+ int timeStyle = DateFormat.NONE;
+ switch (kind) {
+ case "date":
+ dateStyle = getDateTimeStyle(fixedOptions, "style");
+ break;
+ case "time":
+ timeStyle = getDateTimeStyle(fixedOptions, "style");
+ break;
+ case "datetime": // $FALL-THROUGH$
+ default:
+ dateStyle = getDateTimeStyle(fixedOptions, "dateStyle");
+ timeStyle = getDateTimeStyle(fixedOptions, "timeStyle");
+ break;
+ }
// TODO: how to handle conflicts. What if we have both skeleton and style, or pattern?
- Object opt = fixedOptions.get("skeleton");
- if (opt != null) {
- String skeleton = Objects.toString(opt);
- df = DateFormat.getInstanceForSkeleton(skeleton, locale);
- return new DateTimeFormatter(df);
- }
-
- opt = fixedOptions.get("pattern");
- if (opt != null) {
- String pattern = Objects.toString(opt);
- SimpleDateFormat sf = new SimpleDateFormat(pattern, locale);
- return new DateTimeFormatter(sf);
- }
-
- int dateStyle = DateFormat.NONE;
- opt = fixedOptions.get("datestyle");
- if (opt != null) {
- dateStyle = stringToStyle(Objects.toString(opt, ""));
- }
-
- int timeStyle = DateFormat.NONE;
- opt = fixedOptions.get("timestyle");
- if (opt != null) {
- timeStyle = stringToStyle(Objects.toString(opt, ""));
- }
-
if (dateStyle == DateFormat.NONE && timeStyle == DateFormat.NONE) {
- // Match the MessageFormat behavior
- dateStyle = DateFormat.SHORT;
- timeStyle = DateFormat.SHORT;
- }
- df = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
+ String skeleton = "";
+ switch (kind) {
+ case "date":
+ skeleton = getDateFieldOptions(fixedOptions);
+ break;
+ case "time":
+ skeleton = getTimeFieldOptions(fixedOptions);
+ break;
+ case "datetime": // $FALL-THROUGH$
+ default:
+ skeleton = getDateFieldOptions(fixedOptions);
+ skeleton += getTimeFieldOptions(fixedOptions);
+ break;
+ }
- return new DateTimeFormatter(df);
+ if (skeleton.isEmpty()) {
+ // Custom option, icu namespace
+ skeleton = OptUtils.getString(fixedOptions, "icu:skeleton", "");
+ }
+ if (!skeleton.isEmpty()) {
+ DateFormat df = DateFormat.getInstanceForSkeleton(skeleton, locale);
+ return new DateTimeFormatter(locale, df);
+ }
+
+ // No skeletons, custom or otherwise, match fallback to short / short as per spec.
+ switch (kind) {
+ case "date":
+ dateStyle = DateFormat.SHORT;
+ timeStyle = DateFormat.NONE;
+ break;
+ case "time":
+ dateStyle = DateFormat.NONE;
+ timeStyle = DateFormat.SHORT;
+ break;
+ case "datetime": // $FALL-THROUGH$
+ default:
+ dateStyle = DateFormat.SHORT;
+ timeStyle = DateFormat.SHORT;
+ }
+ }
+
+ DateFormat df = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
+ return new DateTimeFormatter(locale, df);
+ }
+
+ private static int getDateTimeStyle(Map<String, Object> options, String key) {
+ String opt = OptUtils.getString(options, key);
+ if (opt != null) {
+ return stringToStyle(opt);
+ }
+ return DateFormat.NONE;
+ }
+
+ private static String getDateFieldOptions(Map<String, Object> options) {
+ StringBuilder skeleton = new StringBuilder();
+ String opt;
+
+ // In all the switches below we just ignore invalid options.
+ // Would be nice to report (log?), but ICU does not have a clear policy on how to do that.
+ // But we don't want to throw, that is too drastic.
+
+ opt = OptUtils.getString(options, "weekday", "");
+ switch (opt) {
+ case "long":
+ skeleton.append("EEEE");
+ break;
+ case "short":
+ skeleton.append("E");
+ break;
+ case "narrow":
+ skeleton.append("EEEEEE");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+
+ opt = OptUtils.getString(options, "era", "");
+ switch (opt) {
+ case "long":
+ skeleton.append("GGGG");
+ break;
+ case "short":
+ skeleton.append("G");
+ break;
+ case "narrow":
+ skeleton.append("GGGGG");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+
+ opt = OptUtils.getString(options, "year", "");
+ switch (opt) {
+ case "numeric":
+ skeleton.append("y");
+ break;
+ case "2-digit":
+ skeleton.append("yy");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+
+ opt = OptUtils.getString(options, "month", "");
+ switch (opt) {
+ case "numeric":
+ skeleton.append("M");
+ break;
+ case "2-digit":
+ skeleton.append("MM");
+ break;
+ case "long":
+ skeleton.append("MMMM");
+ break;
+ case "short":
+ skeleton.append("MMM");
+ break;
+ case "narrow":
+ skeleton.append("MMMMM");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+
+ opt = OptUtils.getString(options, "day", "");
+ switch (opt) {
+ case "numeric":
+ skeleton.append("d");
+ break;
+ case "2-digit":
+ skeleton.append("dd");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+ return skeleton.toString();
+ }
+
+ private static String getTimeFieldOptions(Map<String, Object> options) {
+ StringBuilder skeleton = new StringBuilder();
+ String opt;
+
+ // In all the switches below we just ignore invalid options.
+ // Would be nice to report (log?), but ICU does not have a clear policy on how to do that.
+ // But we don't want to throw, that is too drastic.
+
+ int showHour = 0;
+ opt = OptUtils.getString(options, "hour", "");
+ switch (opt) {
+ case "numeric":
+ showHour = 1;
+ break;
+ case "2-digit":
+ showHour = 2;
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+ if (showHour > 0) {
+ String hourCycle = "";
+ opt = OptUtils.getString(options, "hourCycle", "");
+ switch (opt) {
+ case "h11":
+ hourCycle = "K";
+ break;
+ case "h12":
+ hourCycle = "h";
+ break;
+ case "h23":
+ hourCycle = "H";
+ break;
+ case "h24":
+ hourCycle = "k";
+ break;
+ default:
+ hourCycle = "j"; // default for the locale
+ }
+ skeleton.append(hourCycle);
+ if (showHour == 2) {
+ skeleton.append(hourCycle);
+ }
+ }
+
+ opt = OptUtils.getString(options, "minute", "");
+ switch (opt) {
+ case "numeric":
+ skeleton.append("m");
+ break;
+ case "2-digit":
+ skeleton.append("mm");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+
+ opt = OptUtils.getString(options, "second", "");
+ switch (opt) {
+ case "numeric":
+ skeleton.append("s");
+ break;
+ case "2-digit":
+ skeleton.append("ss");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+
+ opt = OptUtils.getString(options, "fractionalSecondDigits", "");
+ switch (opt) {
+ case "1":
+ skeleton.append("S");
+ break;
+ case "2":
+ skeleton.append("SS");
+ break;
+ case "3":
+ skeleton.append("SSS");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+
+ opt = OptUtils.getString(options, "timeZoneName", "");
+ switch (opt) {
+ case "long":
+ skeleton.append("z");
+ break;
+ case "short":
+ skeleton.append("zzzz");
+ break;
+ case "shortOffset":
+ skeleton.append("O");
+ break;
+ case "longOffset":
+ skeleton.append("OOOO");
+ break;
+ case "shortGeneric":
+ skeleton.append("v");
+ break;
+ case "longGeneric":
+ skeleton.append("vvvv");
+ break;
+ default:
+ // invalid value, we just ignore it.
+ }
+
+ return skeleton.toString();
}
private static class DateTimeFormatter implements Formatter {
private final DateFormat icuFormatter;
+ private final Locale locale;
- private DateTimeFormatter(DateFormat df) {
+ private DateTimeFormatter(Locale locale, DateFormat df) {
+ this.locale = locale;
this.icuFormatter = df;
}
@@ -91,7 +341,24 @@
public FormattedPlaceholder format(Object toFormat, Map<String, Object> variableOptions) {
// TODO: use a special type to indicate function without input argument.
if (toFormat == null) {
- throw new IllegalArgumentException("The date to format can't be null");
+ return null;
+ }
+ if (toFormat instanceof CharSequence) {
+ toFormat = parseIso8601(toFormat.toString());
+ // We were unable to parse the input as iso date
+ if (toFormat instanceof CharSequence) {
+ return new FormattedPlaceholder(
+ toFormat, new PlainStringFormattedValue("{|" + toFormat + "|}"));
+ }
+ }
+ if (toFormat instanceof Calendar) {
+ TimeZone tz = ((Calendar) toFormat).getTimeZone();
+ long milis = ((Calendar) toFormat).getTimeInMillis();
+ android.icu.util.TimeZone icuTz = android.icu.util.TimeZone.getTimeZone(tz.getID());
+ android.icu.util.Calendar calendar =
+ android.icu.util.Calendar.getInstance(icuTz, locale);
+ calendar.setTimeInMillis(milis);
+ toFormat = calendar;
}
String result = icuFormatter.format(toFormat);
return new FormattedPlaceholder(toFormat, new PlainStringFormattedValue(result));
@@ -102,7 +369,68 @@
*/
@Override
public String formatToString(Object toFormat, Map<String, Object> variableOptions) {
- return format(toFormat, variableOptions).toString();
+ FormattedPlaceholder result = format(toFormat, variableOptions);
+ return result != null ? result.toString() : null;
}
}
+
+ private final static Pattern ISO_PATTERN = Pattern.compile(
+ "^(([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])){1}(T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(\\.[0-9]{1,3})?(Z|[+-]((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?$");
+
+ private static Integer safeParse(String str) {
+ if (str == null || str.isEmpty())
+ return null;
+ return Integer.parseInt(str);
+ }
+
+ private static Object parseIso8601(String text) {
+ Matcher m = ISO_PATTERN.matcher(text);
+ if (m.find() && m.groupCount() == 12 && !m.group().isEmpty()) {
+ Integer year = safeParse(m.group(2));
+ Integer month = safeParse(m.group(3));
+ Integer day = safeParse(m.group(4));
+ Integer hour = safeParse(m.group(6));
+ Integer minute = safeParse(m.group(7));
+ Integer second = safeParse(m.group(8));
+ Integer millisecond = 0;
+ if (m.group(9) != null) {
+ String z = (m.group(9) + "000").substring(1, 4);
+ millisecond = safeParse(z);
+ } else {
+ millisecond = 0;
+ }
+ String tzPart = m.group(10);
+
+ if (hour == null) {
+ hour = 0;
+ minute = 0;
+ second = 0;
+ }
+
+ android.icu.util.GregorianCalendar gc = new android.icu.util.GregorianCalendar(
+ year, month - 1, day, hour, minute, second);
+ gc.set(android.icu.util.Calendar.MILLISECOND, millisecond);
+
+ if (tzPart != null) {
+ if (tzPart.equals("Z")) {
+ gc.setTimeZone(android.icu.util.TimeZone.GMT_ZONE);
+ } else {
+ int sign = tzPart.startsWith("-") ? -1 : 1;
+ String[] tzParts = tzPart.substring(1).split(":");
+ if (tzParts.length == 2) {
+ Integer tzHour = safeParse(tzParts[0]);
+ Integer tzMin = safeParse(tzParts[1]);
+ if (tzHour != null && tzMin != null) {
+ int offset = sign * (tzHour * 60 + tzMin) * 60 * 1000;
+ gc.setTimeZone(new android.icu.util.SimpleTimeZone(offset, "offset"));
+ }
+ }
+ }
+ }
+
+ return gc;
+ }
+ return text;
+ }
+
}
diff --git a/android_icu4j/src/main/java/android/icu/message2/FormattedMessage.java b/android_icu4j/src/main/java/android/icu/message2/FormattedMessage.java
index 960723a..d9b4197 100644
--- a/android_icu4j/src/main/java/android/icu/message2/FormattedMessage.java
+++ b/android_icu4j/src/main/java/android/icu/message2/FormattedMessage.java
@@ -1,6 +1,6 @@
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2022 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
+// License & terms of use: https://www.unicode.org/copyright.html
package android.icu.message2;
@@ -118,5 +118,4 @@
public AttributedCharacterIterator toCharacterIterator() {
throw new RuntimeException("Not yet implemented.");
}
-
}
diff --git a/android_icu4j/src/main/java/android/icu/message2/FormattedPlaceholder.java b/android_icu4j/src/main/java/android/icu/message2/FormattedPlaceholder.java
index 16bd52d..aa139db 100644
--- a/android_icu4j/src/main/java/android/icu/message2/FormattedPlaceholder.java
+++ b/android_icu4j/src/main/java/android/icu/message2/FormattedPlaceholder.java
@@ -1,6 +1,6 @@
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2022 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
+// License & terms of use: https://www.unicode.org/copyright.html
package android.icu.message2;
diff --git a/android_icu4j/src/main/java/android/icu/message2/Formatter.java b/android_icu4j/src/main/java/android/icu/message2/Formatter.java
index 0eeac56..2572d3f 100644
--- a/android_icu4j/src/main/java/android/icu/message2/Formatter.java
+++ b/android_icu4j/src/main/java/android/icu/message2/Formatter.java
@@ -1,6 +1,6 @@
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2022 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
+// License & terms of use: https://www.unicode.org/copyright.html
package android.icu.message2;
diff --git a/android_icu4j/src/main/java/android/icu/message2/FormatterFactory.java b/android_icu4j/src/main/java/android/icu/message2/FormatterFactory.java
index 6b89c31..0d1832d 100644
--- a/android_icu4j/src/main/java/android/icu/message2/FormatterFactory.java
+++ b/android_icu4j/src/main/java/android/icu/message2/FormatterFactory.java
@@ -1,6 +1,6 @@
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2022 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
+// License & terms of use: https://www.unicode.org/copyright.html
package android.icu.message2;
diff --git a/android_icu4j/src/main/java/android/icu/message2/IdentityFormatterFactory.java b/android_icu4j/src/main/java/android/icu/message2/IdentityFormatterFactory.java
index 6798b98..7057ae4 100644
--- a/android_icu4j/src/main/java/android/icu/message2/IdentityFormatterFactory.java
+++ b/android_icu4j/src/main/java/android/icu/message2/IdentityFormatterFactory.java
@@ -1,6 +1,6 @@
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2022 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
+// License & terms of use: https://www.unicode.org/copyright.html
package android.icu.message2;
@@ -26,7 +26,8 @@
*/
@Override
public FormattedPlaceholder format(Object toFormat, Map<String, Object> variableOptions) {
- return new FormattedPlaceholder(toFormat, new PlainStringFormattedValue(Objects.toString(toFormat)));
+ return new FormattedPlaceholder(
+ toFormat, new PlainStringFormattedValue(Objects.toString(toFormat)));
}
/**
diff --git a/android_icu4j/src/main/java/android/icu/message2/InputSource.java b/android_icu4j/src/main/java/android/icu/message2/InputSource.java
new file mode 100644
index 0000000..7122486
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/InputSource.java
@@ -0,0 +1,85 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2024 and later: Unicode, Inc. and others.
+// License & terms of use: https://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+class InputSource {
+ final String buffer;
+
+ private int cursor;
+ private int lastReadCursor = -1;
+ private int lastReadCount = 0;
+
+ InputSource(String input) {
+ if (input == null) {
+ throw new IllegalArgumentException("Input string should not be null");
+ }
+ this.buffer = input;
+ this.cursor = 0;
+ }
+
+ boolean atEnd() {
+ return cursor >= buffer.length();
+ }
+
+ int peekChar() {
+ if (atEnd()) {
+ return -1;
+ }
+ return buffer.charAt(cursor);
+ }
+
+ int readCodePoint() {
+ // TODO: remove this?
+ // START Detect possible infinite loop
+ if (lastReadCursor != cursor) {
+ lastReadCursor = cursor;
+ lastReadCount = 1;
+ } else {
+ lastReadCount++;
+ if (lastReadCount >= 10) {
+ throw new RuntimeException("Stuck in a loop!");
+ }
+ }
+ // END Detect possible infinite loop
+
+ if (atEnd()) {
+ return -1;
+ }
+
+ char c = buffer.charAt(cursor++);
+ if (Character.isHighSurrogate(c)) {
+ if (!atEnd()) {
+ char c2 = buffer.charAt(cursor++);
+ if (Character.isLowSurrogate(c2)) {
+ return Character.toCodePoint(c, c2);
+ } else { // invalid, high surrogate followed by non-surrogate
+ cursor--;
+ return c;
+ }
+ }
+ }
+ return c;
+ }
+
+ // Backup a number of characters.
+ void backup(int amount) {
+ // TODO: validate
+ cursor -= amount;
+ }
+
+ int getPosition() {
+ return cursor;
+ }
+
+ void skip(int amount) {
+ // TODO: validate
+ cursor += amount;
+ }
+
+ void gotoPosition(int position) {
+ // TODO: validate
+ cursor = position;
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/MFDataModel.java b/android_icu4j/src/main/java/android/icu/message2/MFDataModel.java
new file mode 100644
index 0000000..4436f57
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/MFDataModel.java
@@ -0,0 +1,523 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: https://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This maps closely to the official specification.
+ * Since it is not final, we will not add javadoc everywhere.
+ *
+ * <p>See <a target="github" href="https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model/README.md">the
+ * latest description</a>.</p>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+@SuppressWarnings("javadoc")
+public class MFDataModel {
+
+ private MFDataModel() {
+ // Prevent instantiation
+ }
+
+ // Messages
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public interface Message {
+ // Provides a common type for PatternMessage and SelectMessage.
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class PatternMessage implements Message {
+ public final List<Declaration> declarations;
+ public final Pattern pattern;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public PatternMessage(List<Declaration> declarations, Pattern pattern) {
+ this.declarations = declarations;
+ this.pattern = pattern;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class SelectMessage implements Message {
+ public final List<Declaration> declarations;
+ public final List<Expression> selectors;
+ public final List<Variant> variants;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public SelectMessage(
+ List<Declaration> declarations,
+ List<Expression> selectors,
+ List<Variant> variants) {
+ this.declarations = declarations;
+ this.selectors = selectors;
+ this.variants = variants;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public interface Declaration {
+ // Provides a common type for InputDeclaration, LocalDeclaration, and UnsupportedStatement.
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class InputDeclaration implements Declaration {
+ public final String name;
+ public final VariableExpression value;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public InputDeclaration(String name, VariableExpression value) {
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class LocalDeclaration implements Declaration {
+ public final String name;
+ public final Expression value;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public LocalDeclaration(String name, Expression value) {
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class UnsupportedStatement implements Declaration {
+ public final String keyword;
+ public final String body;
+ public final List<Expression> expressions;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public UnsupportedStatement(String keyword, String body, List<Expression> expressions) {
+ this.keyword = keyword;
+ this.body = body;
+ this.expressions = expressions;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public interface LiteralOrCatchallKey {
+ // Provides a common type for the selection keys: Variant, Literal, or CatchallKey.
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Variant implements LiteralOrCatchallKey {
+ public final List<LiteralOrCatchallKey> keys;
+ public final Pattern value;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Variant(List<LiteralOrCatchallKey> keys, Pattern value) {
+ this.keys = keys;
+ this.value = value;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class CatchallKey implements LiteralOrCatchallKey {
+ // String value; // Always '*' in MF2
+ }
+
+ // Patterns
+
+ // type Pattern = Array<string | Expression | Markup>;
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Pattern {
+ public final List<PatternPart> parts;
+
+ Pattern() {
+ this.parts = new ArrayList<>();
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public interface PatternPart {
+ // Provides a common type for StringPart and Expression.
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class StringPart implements PatternPart {
+ public final String value;
+
+ StringPart(String value) {
+ this.value = value;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public interface Expression extends PatternPart {
+ // Provides a common type for all kind of expressions:
+ // LiteralExpression, VariableExpression, FunctionExpression, UnsupportedExpression, Markup
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class LiteralExpression implements Expression {
+ public final Literal arg;
+ public final Annotation annotation;
+ public final List<Attribute> attributes;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public LiteralExpression(Literal arg, Annotation annotation, List<Attribute> attributes) {
+ this.arg = arg;
+ this.annotation = annotation;
+ this.attributes = attributes;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class VariableExpression implements Expression {
+ public final VariableRef arg;
+ public final Annotation annotation;
+ public final List<Attribute> attributes;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public VariableExpression(
+ VariableRef arg, Annotation annotation, List<Attribute> attributes) {
+ this.arg = arg;
+ this.annotation = annotation;
+ this.attributes = attributes;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public interface Annotation {
+ // Provides a common type for FunctionAnnotation, UnsupportedAnnotation
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class FunctionExpression implements Expression {
+ public final FunctionAnnotation annotation;
+ public final List<Attribute> attributes;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public FunctionExpression(FunctionAnnotation annotation, List<Attribute> attributes) {
+ this.annotation = annotation;
+ this.attributes = attributes;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class UnsupportedExpression implements Expression {
+ public final UnsupportedAnnotation annotation;
+ public final List<Attribute> attributes;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public UnsupportedExpression(UnsupportedAnnotation annotation, List<Attribute> attributes) {
+ this.annotation = annotation;
+ this.attributes = attributes;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Attribute {
+ public final String name;
+ public final LiteralOrVariableRef value;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Attribute(String name, LiteralOrVariableRef value) {
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ // Expressions
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public interface LiteralOrVariableRef {
+ // Provides a common type for Literal and VariableRef,
+ // to represent things like `foo` / `|foo|` / `1234` (literals)
+ // and `$foo` (VariableRef), as argument for placeholders or value in options.
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Literal implements LiteralOrVariableRef, LiteralOrCatchallKey {
+ public final String value;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Literal(String value) {
+ this.value = value;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class VariableRef implements LiteralOrVariableRef {
+ public final String name;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public VariableRef(String name) {
+ this.name = name;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class FunctionAnnotation implements Annotation {
+ public final String name;
+ public final Map<String, Option> options;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public FunctionAnnotation(String name, Map<String, Option> options) {
+ this.name = name;
+ this.options = options;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Option {
+ public final String name;
+ public final LiteralOrVariableRef value;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Option(String name, LiteralOrVariableRef value) {
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class UnsupportedAnnotation implements Annotation {
+ public final String source;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public UnsupportedAnnotation(String source) {
+ this.source = source;
+ }
+ }
+
+ // Markup
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Markup implements Expression {
+ enum Kind {
+ OPEN,
+ CLOSE,
+ STANDALONE
+ }
+
+ public final Kind kind;
+ public final String name;
+ public final Map<String, Option> options;
+ public final List<Attribute> attributes;
+
+ /**
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Markup(
+ Kind kind, String name, Map<String, Option> options, List<Attribute> attributes) {
+ this.kind = kind;
+ this.name = name;
+ this.options = options;
+ this.attributes = attributes;
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/MFDataModelFormatter.java b/android_icu4j/src/main/java/android/icu/message2/MFDataModelFormatter.java
new file mode 100644
index 0000000..347c4cc
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/MFDataModelFormatter.java
@@ -0,0 +1,602 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: https://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import android.icu.message2.MFDataModel.Annotation;
+import android.icu.message2.MFDataModel.CatchallKey;
+import android.icu.message2.MFDataModel.Declaration;
+import android.icu.message2.MFDataModel.Expression;
+import android.icu.message2.MFDataModel.FunctionAnnotation;
+import android.icu.message2.MFDataModel.FunctionExpression;
+import android.icu.message2.MFDataModel.InputDeclaration;
+import android.icu.message2.MFDataModel.Literal;
+import android.icu.message2.MFDataModel.LiteralExpression;
+import android.icu.message2.MFDataModel.LiteralOrCatchallKey;
+import android.icu.message2.MFDataModel.LiteralOrVariableRef;
+import android.icu.message2.MFDataModel.LocalDeclaration;
+import android.icu.message2.MFDataModel.Option;
+import android.icu.message2.MFDataModel.Pattern;
+import android.icu.message2.MFDataModel.SelectMessage;
+import android.icu.message2.MFDataModel.StringPart;
+import android.icu.message2.MFDataModel.UnsupportedAnnotation;
+import android.icu.message2.MFDataModel.UnsupportedExpression;
+import android.icu.message2.MFDataModel.VariableRef;
+import android.icu.message2.MFDataModel.Variant;
+import android.icu.util.Calendar;
+import android.icu.util.CurrencyAmount;
+
+/**
+ * Takes an {@link MFDataModel} and formats it to a {@link String}
+ * (and later on we will also implement formatting to a {@code FormattedMessage}).
+ */
+// TODO: move this in the MessageFormatter?
+class MFDataModelFormatter {
+ private final Locale locale;
+ private final MFDataModel.Message dm;
+
+ private final MFFunctionRegistry standardFunctions;
+ private final MFFunctionRegistry customFunctions;
+ private static final MFFunctionRegistry EMPTY_REGISTY = MFFunctionRegistry.builder().build();
+
+ MFDataModelFormatter(
+ MFDataModel.Message dm, Locale locale, MFFunctionRegistry customFunctionRegistry) {
+ this.locale = locale;
+ this.dm = dm;
+ this.customFunctions =
+ customFunctionRegistry == null ? EMPTY_REGISTY : customFunctionRegistry;
+
+ standardFunctions =
+ MFFunctionRegistry.builder()
+ // Date/time formatting
+ .setFormatter("datetime", new DateTimeFormatterFactory("datetime"))
+ .setFormatter("date", new DateTimeFormatterFactory("date"))
+ .setFormatter("time", new DateTimeFormatterFactory("time"))
+ .setDefaultFormatterNameForType(Date.class, "datetime")
+ .setDefaultFormatterNameForType(Calendar.class, "datetime")
+ .setDefaultFormatterNameForType(java.util.Calendar.class, "datetime")
+
+ // Number formatting
+ .setFormatter("number", new NumberFormatterFactory("number"))
+ .setFormatter("integer", new NumberFormatterFactory("integer"))
+ .setDefaultFormatterNameForType(Integer.class, "number")
+ .setDefaultFormatterNameForType(Double.class, "number")
+ .setDefaultFormatterNameForType(Number.class, "number")
+ .setDefaultFormatterNameForType(CurrencyAmount.class, "number")
+
+ // Format that returns "to string"
+ .setFormatter("string", new IdentityFormatterFactory())
+ .setDefaultFormatterNameForType(String.class, "string")
+ .setDefaultFormatterNameForType(CharSequence.class, "string")
+
+ // Register the standard selectors
+ .setSelector("number", new NumberFormatterFactory("number"))
+ .setSelector("integer", new NumberFormatterFactory("integer"))
+ .setSelector("string", new TextSelectorFactory())
+ .setSelector("icu:gender", new TextSelectorFactory())
+ .build();
+ }
+
+ String format(Map<String, Object> arguments) {
+ MFDataModel.Pattern patternToRender = null;
+ if (arguments == null) {
+ arguments = new HashMap<>();
+ }
+
+ Map<String, Object> variables;
+ if (dm instanceof MFDataModel.PatternMessage) {
+ MFDataModel.PatternMessage pm = (MFDataModel.PatternMessage) dm;
+ variables = resolveDeclarations(pm.declarations, arguments);
+ patternToRender = pm.pattern;
+ } else if (dm instanceof MFDataModel.SelectMessage) {
+ MFDataModel.SelectMessage sm = (MFDataModel.SelectMessage) dm;
+ variables = resolveDeclarations(sm.declarations, arguments);
+ patternToRender = findBestMatchingPattern(sm, variables, arguments);
+ } else {
+ formattingError("");
+ return "ERROR!";
+ }
+
+ if (patternToRender == null) {
+ return "ERROR!";
+ }
+
+ StringBuilder result = new StringBuilder();
+ for (MFDataModel.PatternPart part : patternToRender.parts) {
+ if (part instanceof MFDataModel.StringPart) {
+ MFDataModel.StringPart strPart = (StringPart) part;
+ result.append(strPart.value);
+ } else if (part instanceof MFDataModel.Expression) {
+ FormattedPlaceholder formattedExpression =
+ formatExpression((Expression) part, variables, arguments);
+ result.append(formattedExpression.getFormattedValue().toString());
+ } else if (part instanceof MFDataModel.Markup) {
+ // Ignore
+ } else if (part instanceof MFDataModel.UnsupportedExpression) {
+ // Ignore
+ } else {
+ formattingError("Unknown part type: " + part);
+ }
+ }
+ return result.toString();
+ }
+
+ private Pattern findBestMatchingPattern(
+ SelectMessage sm, Map<String, Object> variables, Map<String, Object> arguments) {
+ Pattern patternToRender = null;
+
+ // ====================================
+ // spec: ### Resolve Selectors
+ // ====================================
+
+ // Collect all the selector functions in an array, to reuse
+ List<Expression> selectors = sm.selectors;
+ // spec: Let `res` be a new empty list of resolved values that support selection.
+ List<ResolvedSelector> res = new ArrayList<>(selectors.size());
+ // spec: For each _selector_ `sel`, in source order,
+ for (Expression sel : selectors) {
+ // spec: Let `rv` be the resolved value of `sel`.
+ FormattedPlaceholder fph = formatExpression(sel, variables, arguments);
+ String functionName = null;
+ Object argument = null;
+ Map<String, Object> options = new HashMap<>();
+ if (fph.getInput() instanceof ResolvedExpression) {
+ ResolvedExpression re = (ResolvedExpression) fph.getInput();
+ argument = re.argument;
+ functionName = re.functionName;
+ options.putAll(re.options);
+ } else if (fph.getInput() instanceof MFDataModel.VariableExpression) {
+ MFDataModel.VariableExpression ve = (MFDataModel.VariableExpression) fph.getInput();
+ argument = resolveLiteralOrVariable(ve.arg, variables, arguments);
+ if (ve.annotation instanceof FunctionAnnotation) {
+ functionName = ((FunctionAnnotation) ve.annotation).name;
+ }
+ } else if (fph.getInput() instanceof LiteralExpression) {
+ LiteralExpression le = (LiteralExpression) fph.getInput();
+ argument = le.arg;
+ if (le.annotation instanceof FunctionAnnotation) {
+ functionName = ((FunctionAnnotation) le.annotation).name;
+ }
+ }
+ SelectorFactory funcFactory = standardFunctions.getSelector(functionName);
+ if (funcFactory == null) {
+ funcFactory = customFunctions.getSelector(functionName);
+ }
+ // spec: If selection is supported for `rv`:
+ if (funcFactory != null) {
+ Selector selectorFunction = funcFactory.createSelector(locale, options);
+ ResolvedSelector rs = new ResolvedSelector(argument, options, selectorFunction);
+ // spec: Append `rv` as the last element of the list `res`.
+ res.add(rs);
+ } else {
+ throw new IllegalArgumentException("Unknown selector type: " + functionName);
+ }
+ }
+
+ // This should not be possible, we added one function for each selector,
+ // or we have thrown an exception.
+ // But just in case someone removes the throw above?
+ if (res.size() != selectors.size()) {
+ throw new IllegalArgumentException(
+ "Something went wrong, not enough selector functions, "
+ + res.size() + " vs. " + selectors.size());
+ }
+
+ // ====================================
+ // spec: ### Resolve Preferences
+ // ====================================
+
+ // spec: Let `pref` be a new empty list of lists of strings.
+ List<List<String>> pref = new ArrayList<>();
+ // spec: For each index `i` in `res`:
+ for (int i = 0; i < res.size(); i++) {
+ // spec: Let `keys` be a new empty list of strings.
+ List<String> keys = new ArrayList<>();
+ // spec: For each _variant_ `var` of the message:
+ for (Variant var : sm.variants) {
+ // spec: Let `key` be the `var` key at position `i`.
+ LiteralOrCatchallKey key = var.keys.get(i);
+ // spec: If `key` is not the catch-all key `'*'`:
+ if (key instanceof CatchallKey) {
+ keys.add("*");
+ } else if (key instanceof Literal) {
+ // spec: Assert that `key` is a _literal_.
+ // spec: Let `ks` be the resolved value of `key`.
+ String ks = ((Literal) key).value;
+ // spec: Append `ks` as the last element of the list `keys`.
+ keys.add(ks);
+ } else {
+ formattingError("Literal expected, but got " + key);
+ }
+ }
+ // spec: Let `rv` be the resolved value at index `i` of `res`.
+ ResolvedSelector rv = res.get(i);
+ // spec: Let `matches` be the result of calling the method MatchSelectorKeys(`rv`, `keys`)
+ List<String> matches = matchSelectorKeys(rv, keys);
+ // spec: Append `matches` as the last element of the list `pref`.
+ pref.add(matches);
+ }
+
+ // ====================================
+ // spec: ### Filter Variants
+ // ====================================
+
+ // spec: Let `vars` be a new empty list of _variants_.
+ List<Variant> vars = new ArrayList<>();
+ // spec: For each _variant_ `var` of the message:
+ for (Variant var : sm.variants) {
+ // spec: For each index `i` in `pref`:
+ int found = 0;
+ for (int i = 0; i < pref.size(); i++) {
+ // spec: Let `key` be the `var` key at position `i`.
+ LiteralOrCatchallKey key = var.keys.get(i);
+ // spec: If `key` is the catch-all key `'*'`:
+ if (key instanceof CatchallKey) {
+ // spec: Continue the inner loop on `pref`.
+ found++;
+ continue;
+ }
+ // spec: Assert that `key` is a _literal_.
+ if (!(key instanceof Literal)) {
+ formattingError("Literal expected");
+ }
+ // spec: Let `ks` be the resolved value of `key`.
+ String ks = ((Literal) key).value;
+ // spec: Let `matches` be the list of strings at index `i` of `pref`.
+ List<String> matches = pref.get(i);
+ // spec: If `matches` includes `ks`:
+ if (matches.contains(ks)) {
+ // spec: Continue the inner loop on `pref`.
+ found++;
+ continue;
+ } else {
+ // spec: Else:
+ // spec: Continue the outer loop on message _variants_.
+ break;
+ }
+ }
+ if (found == pref.size()) {
+ // spec: Append `var` as the last element of the list `vars`.
+ vars.add(var);
+ }
+ }
+
+ // ====================================
+ // spec: ### Sort Variants
+ // ====================================
+ // spec: Let `sortable` be a new empty list of (integer, _variant_) tuples.
+ List<IntVarTuple> sortable = new ArrayList<>();
+ // spec: For each _variant_ `var` of `vars`:
+ for (Variant var : vars) {
+ // spec: Let `tuple` be a new tuple (-1, `var`).
+ IntVarTuple tuple = new IntVarTuple(-1, var);
+ // spec: Append `tuple` as the last element of the list `sortable`.
+ sortable.add(tuple);
+ }
+ // spec: Let `len` be the integer count of items in `pref`.
+ int len = pref.size();
+ // spec: Let `i` be `len` - 1.
+ int i = len - 1;
+ // spec: While `i` >= 0:
+ while (i >= 0) {
+ // spec: Let `matches` be the list of strings at index `i` of `pref`.
+ List<String> matches = pref.get(i);
+ // spec: Let `minpref` be the integer count of items in `matches`.
+ int minpref = matches.size();
+ // spec: For each tuple `tuple` of `sortable`:
+ for (IntVarTuple tuple : sortable) {
+ // spec: Let `matchpref` be an integer with the value `minpref`.
+ int matchpref = minpref;
+ // spec: Let `key` be the `tuple` _variant_ key at position `i`.
+ LiteralOrCatchallKey key = tuple.variant.keys.get(i);
+ // spec: If `key` is not the catch-all key `'*'`:
+ if (!(key instanceof CatchallKey)) {
+ // spec: Assert that `key` is a _literal_.
+ if (!(key instanceof Literal)) {
+ formattingError("Literal expected");
+ }
+ // spec: Let `ks` be the resolved value of `key`.
+ String ks = ((Literal) key).value;
+ // spec: Let `matchpref` be the integer position of `ks` in `matches`.
+ matchpref = matches.indexOf(ks);
+ }
+ // spec: Set the `tuple` integer value as `matchpref`.
+ tuple.integer = matchpref;
+ }
+ // spec: Set `sortable` to be the result of calling the method `SortVariants(sortable)`.
+ sortable.sort(MFDataModelFormatter::sortVariants);
+ // spec: Set `i` to be `i` - 1.
+ i--;
+ }
+ // spec: Let `var` be the _variant_ element of the first element of `sortable`.
+ IntVarTuple var = sortable.get(0);
+ // spec: Select the _pattern_ of `var`.
+ patternToRender = var.variant.value;
+
+ // And should do that only once, when building the data model.
+ if (patternToRender == null) {
+ // If there was a case with all entries in the keys `*` this should not happen
+ throw new IllegalArgumentException(
+ "The selection went wrong, cannot select any option.");
+ }
+
+ return patternToRender;
+ }
+
+ /* spec:
+ * `SortVariants` is a method whose single argument is
+ * a list of (integer, _variant_) tuples.
+ * It returns a list of (integer, _variant_) tuples.
+ * Any implementation of `SortVariants` is acceptable
+ * as long as it satisfies the following requirements:
+ *
+ * 1. Let `sortable` be an arbitrary list of (integer, _variant_) tuples.
+ * 1. Let `sorted` be `SortVariants(sortable)`.
+ * 1. `sorted` is the result of sorting `sortable` using the following comparator:
+ * 1. `(i1, v1)` <= `(i2, v2)` if and only if `i1 <= i2`.
+ * 1. The sort is stable (pairs of tuples from `sortable` that are equal
+ * in their first element have the same relative order in `sorted`).
+ */
+ private static int sortVariants(IntVarTuple o1, IntVarTuple o2) {
+ int result = Integer.compare(o1.integer, o2.integer);
+ if (result != 0) {
+ return result;
+ }
+ List<LiteralOrCatchallKey> v1 = o1.variant.keys;
+ List<LiteralOrCatchallKey> v2 = o1.variant.keys;
+ if (v1.size() != v2.size()) {
+ formattingError("The number of keys is not equal.");
+ }
+ for (int i = 0; i < v1.size(); i++) {
+ LiteralOrCatchallKey k1 = v1.get(i);
+ LiteralOrCatchallKey k2 = v2.get(i);
+ String s1 = k1 instanceof Literal ? ((Literal) k1).value : "*";
+ String s2 = k2 instanceof Literal ? ((Literal) k2).value : "*";
+ int cmp = s1.compareTo(s2);
+ if (cmp != 0) {
+ return cmp;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * spec:
+ * The method MatchSelectorKeys is determined by the implementation.
+ * It takes as arguments a resolved _selector_ value `rv` and a list of string keys `keys`,
+ * and returns a list of string keys in preferential order.
+ * The returned list MUST contain only unique elements of the input list `keys`.
+ * The returned list MAY be empty.
+ * The most-preferred key is first,
+ * with each successive key appearing in order by decreasing preference.
+ */
+ @SuppressWarnings("static-method")
+ private List<String> matchSelectorKeys(ResolvedSelector rv, List<String> keys) {
+ return rv.selectorFunction.matches(rv.argument, keys, rv.options);
+ }
+
+ private static class ResolvedSelector {
+ final Object argument;
+ final Map<String, Object> options;
+ final Selector selectorFunction;
+
+ public ResolvedSelector(
+ Object argument, Map<String, Object> options, Selector selectorFunction) {
+ this.argument = argument;
+ this.options = new HashMap<>(options);
+ this.selectorFunction = selectorFunction;
+ }
+ }
+
+ private static void formattingError(String message) {
+ throw new IllegalArgumentException(message);
+ }
+
+ private FormatterFactory getFormattingFunctionFactoryByName(
+ Object toFormat, String functionName) {
+ // Get a function name from the type of the object to format
+ if (functionName == null || functionName.isEmpty()) {
+ if (toFormat == null) {
+ // The object to format is null, and no function provided.
+ return null;
+ }
+ Class<?> clazz = toFormat.getClass();
+ functionName = standardFunctions.getDefaultFormatterNameForType(clazz);
+ if (functionName == null) {
+ functionName = customFunctions.getDefaultFormatterNameForType(clazz);
+ }
+ if (functionName == null) {
+ throw new IllegalArgumentException(
+ "Object to format without a function, and unknown type: "
+ + toFormat.getClass().getName());
+ }
+ }
+
+ FormatterFactory func = standardFunctions.getFormatter(functionName);
+ if (func == null) {
+ func = customFunctions.getFormatter(functionName);
+ }
+ return func;
+ }
+
+ private static Object resolveLiteralOrVariable(
+ LiteralOrVariableRef value,
+ Map<String, Object> localVars,
+ Map<String, Object> arguments) {
+ if (value instanceof Literal) {
+ String val = ((Literal) value).value;
+ Number nr = OptUtils.asNumber(val);
+ if (nr != null) {
+ return nr;
+ }
+ return val;
+ } else if (value instanceof VariableRef) {
+ String varName = ((VariableRef) value).name;
+ Object val = localVars.get(varName);
+ if (val == null) {
+ val = localVars.get(varName);
+ }
+ if (val == null) {
+ val = arguments.get(varName);
+ }
+ return val;
+ }
+ return value;
+ }
+
+ private static Map<String, Object> convertOptions(
+ Map<String, Option> options,
+ Map<String, Object> localVars,
+ Map<String, Object> arguments) {
+ Map<String, Object> result = new HashMap<>();
+ for (Option option : options.values()) {
+ result.put(option.name, resolveLiteralOrVariable(option.value, localVars, arguments));
+ }
+ return result;
+ }
+
+ /**
+ * Formats an expression.
+ *
+ * @param expression the expression to format
+ * @param variables local variables, created from declarations (`.input` and `.local`)
+ * @param arguments the arguments passed at runtime to be formatted (`mf.format(arguments)`)
+ */
+ private FormattedPlaceholder formatExpression(
+ Expression expression, Map<String, Object> variables, Map<String, Object> arguments) {
+
+ Annotation annotation = null; // function name
+ String functionName = null;
+ Object toFormat = null;
+ Map<String, Object> options = new HashMap<>();
+ String fallbackString = "{\uFFFD}";
+
+ if (expression instanceof MFDataModel.VariableExpression) {
+ MFDataModel.VariableExpression varPart = (MFDataModel.VariableExpression) expression;
+ fallbackString = "{$" + varPart.arg.name + "}";
+ annotation = varPart.annotation; // function name & options
+ Object resolved = resolveLiteralOrVariable(varPart.arg, variables, arguments);
+ if (resolved instanceof FormattedPlaceholder) {
+ Object input = ((FormattedPlaceholder) resolved).getInput();
+ if (input instanceof ResolvedExpression) {
+ ResolvedExpression re = (ResolvedExpression) input;
+ toFormat = re.argument;
+ functionName = re.functionName;
+ options.putAll(re.options);
+ } else {
+ toFormat = input;
+ }
+ } else {
+ toFormat = resolved;
+ }
+ } else if (expression
+ instanceof MFDataModel.FunctionExpression) { // Function without arguments
+ MFDataModel.FunctionExpression fe = (FunctionExpression) expression;
+ fallbackString = "{:" + fe.annotation.name + "}";
+ annotation = fe.annotation;
+ } else if (expression instanceof MFDataModel.LiteralExpression) {
+ MFDataModel.LiteralExpression le = (LiteralExpression) expression;
+ annotation = le.annotation;
+ fallbackString = "{|" + le.arg.value + "|}";
+ toFormat = resolveLiteralOrVariable(le.arg, variables, arguments);
+ } else if (expression instanceof MFDataModel.Markup) {
+ // No output on markup, for now (we only format to string)
+ return new FormattedPlaceholder(expression, new PlainStringFormattedValue(""));
+ } else {
+ UnsupportedExpression ue = (UnsupportedExpression) expression;
+ char sigil = ue.annotation.source.charAt(0);
+ return new FormattedPlaceholder(
+ expression, new PlainStringFormattedValue("{" + sigil + "}"));
+ }
+
+ if (annotation instanceof FunctionAnnotation) {
+ FunctionAnnotation fa = (FunctionAnnotation) annotation;
+ if (functionName != null && !functionName.equals(fa.name)) {
+ formattingError(
+ "invalid function overrides, '" + functionName + "' <> '" + fa.name + "'");
+ }
+ functionName = fa.name;
+ Map<String, Object> newOptions = convertOptions(fa.options, variables, arguments);
+ options.putAll(newOptions);
+ } else if (annotation instanceof UnsupportedAnnotation) {
+ // We don't know how to format unsupported annotations
+ return new FormattedPlaceholder(expression, new PlainStringFormattedValue(fallbackString));
+ }
+
+ FormatterFactory funcFactory = getFormattingFunctionFactoryByName(toFormat, functionName);
+ if (funcFactory == null) {
+ return new FormattedPlaceholder(expression, new PlainStringFormattedValue(fallbackString));
+ }
+ Formatter ff = funcFactory.createFormatter(locale, options);
+ String res = ff.formatToString(toFormat, arguments);
+ if (res == null) {
+ res = fallbackString;
+ }
+
+ ResolvedExpression resExpression = new ResolvedExpression(toFormat, functionName, options);
+ return new FormattedPlaceholder(resExpression, new PlainStringFormattedValue(res));
+ }
+
+ static class ResolvedExpression implements Expression {
+ final Object argument;
+ final String functionName;
+ final Map<String, Object> options;
+
+ public ResolvedExpression(
+ Object argument, String functionName, Map<String, Object> options) {
+ this.argument = argument;
+ this.functionName = functionName;
+ this.options = options;
+ }
+ }
+
+ private Map<String, Object> resolveDeclarations(
+ List<MFDataModel.Declaration> declarations, Map<String, Object> arguments) {
+ Map<String, Object> variables = new HashMap<>();
+ String name;
+ Expression value;
+ if (declarations != null) {
+ for (Declaration declaration : declarations) {
+ if (declaration instanceof InputDeclaration) {
+ name = ((InputDeclaration) declaration).name;
+ value = ((InputDeclaration) declaration).value;
+ } else if (declaration instanceof LocalDeclaration) {
+ name = ((LocalDeclaration) declaration).name;
+ value = ((LocalDeclaration) declaration).value;
+ } else {
+ continue;
+ }
+ try {
+ // There it no need to succeed in solving everything.
+ // For example there is no problem is `$b` is not defined below:
+ // .local $a = {$b :number}
+ // {{ Hello {$user}! }}
+ FormattedPlaceholder fmt = formatExpression(value, variables, arguments);
+ // If it works, all good
+ variables.put(name, fmt);
+ } catch (Exception e) {
+ // It's OK to ignore the failure in this context, see comment above.
+ }
+ }
+ }
+ return variables;
+ }
+
+ private static class IntVarTuple {
+ int integer;
+ final Variant variant;
+
+ public IntVarTuple(int integer, Variant variant) {
+ this.integer = integer;
+ this.variant = variant;
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/MFDataModelValidator.java b/android_icu4j/src/main/java/android/icu/message2/MFDataModelValidator.java
new file mode 100644
index 0000000..aecd281
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/MFDataModelValidator.java
@@ -0,0 +1,205 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2024 and later: Unicode, Inc. and others.
+// License & terms of use: https://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+
+import android.icu.message2.MFDataModel.Annotation;
+import android.icu.message2.MFDataModel.CatchallKey;
+import android.icu.message2.MFDataModel.Declaration;
+import android.icu.message2.MFDataModel.Expression;
+import android.icu.message2.MFDataModel.FunctionAnnotation;
+import android.icu.message2.MFDataModel.FunctionExpression;
+import android.icu.message2.MFDataModel.InputDeclaration;
+import android.icu.message2.MFDataModel.Literal;
+import android.icu.message2.MFDataModel.LiteralExpression;
+import android.icu.message2.MFDataModel.LiteralOrCatchallKey;
+import android.icu.message2.MFDataModel.LiteralOrVariableRef;
+import android.icu.message2.MFDataModel.LocalDeclaration;
+import android.icu.message2.MFDataModel.Option;
+import android.icu.message2.MFDataModel.PatternMessage;
+import android.icu.message2.MFDataModel.SelectMessage;
+import android.icu.message2.MFDataModel.VariableExpression;
+import android.icu.message2.MFDataModel.VariableRef;
+import android.icu.message2.MFDataModel.Variant;
+
+// I can merge all this in the MFDataModel class and make it private
+class MFDataModelValidator {
+ private final MFDataModel.Message message;
+ private final Set<String> declaredVars = new HashSet<>();
+
+ MFDataModelValidator(MFDataModel.Message message) {
+ this.message = message;
+ }
+
+ boolean validate() throws MFParseException {
+ if (message instanceof PatternMessage) {
+ validateDeclarations(((PatternMessage) message).declarations);
+ } else if (message instanceof SelectMessage) {
+ SelectMessage sm = (SelectMessage) message;
+ validateDeclarations(sm.declarations);
+ validateSelectors(sm.selectors);
+ int selectorCount = sm.selectors.size();
+ validateVariants(sm.variants, selectorCount);
+ }
+ return true;
+ }
+
+ private boolean validateVariants(List<Variant> variants, int selectorCount)
+ throws MFParseException {
+ if (variants == null || variants.isEmpty()) {
+ error("Selection messages must have at least one variant");
+ }
+
+ // Look for an entry with all keys = '*'
+ boolean hasUltimateFallback = false;
+ Set<String> fakeKeys = new HashSet<>();
+ for (Variant variant : variants) {
+ if (variant.keys == null || variant.keys.isEmpty()) {
+ error("Selection variants must have at least one key");
+ }
+ if (variant.keys.size() != selectorCount) {
+ error("Selection variants must have the same number of variants as the selectors.");
+ }
+ int catchAllCount = 0;
+ StringJoiner fakeKey = new StringJoiner("<<::>>");
+ for (LiteralOrCatchallKey key : variant.keys) {
+ if (key instanceof CatchallKey) {
+ catchAllCount++;
+ fakeKey.add("*");
+ } else if (key instanceof Literal) {
+ fakeKey.add(((Literal) key).value);
+ }
+ }
+ if (fakeKeys.contains(fakeKey.toString())) {
+ error("Dumplicate combination of keys");
+ } else {
+ fakeKeys.add(fakeKey.toString());
+ }
+ if (catchAllCount == selectorCount) {
+ hasUltimateFallback = true;
+ }
+ }
+ if (!hasUltimateFallback) {
+ error("There must be one variant with all the keys being '*'");
+ }
+ return true;
+ }
+
+ private boolean validateSelectors(List<Expression> selectors) throws MFParseException {
+ if (selectors == null || selectors.isEmpty()) {
+ error("Selection messages must have selectors");
+ }
+ return true;
+ }
+
+ /*
+ * .input {$foo :number} .input {$foo} => ERROR
+ * .input {$foo :number} .local $foo={$bar} => ERROR, local foo overrides an input
+ * .local $foo={...} .local $foo={...} => ERROR, foo declared twice
+ * .local $a={$foo} .local $b={$foo} => NOT AN ERROR (foo is used, not declared)
+ * .local $a={:f opt=$foo} .local $foo={$foo} => ERROR, foo declared after beeing used in opt
+ */
+ private boolean validateDeclarations(List<Declaration> declarations) throws MFParseException {
+ if (declarations == null || declarations.isEmpty()) {
+ return true;
+ }
+ for (Declaration declaration : declarations) {
+ if (declaration instanceof LocalDeclaration) {
+ LocalDeclaration ld = (LocalDeclaration) declaration;
+ validateExpression(ld.value, false);
+ addVariableDeclaration(ld.name);
+ } else if (declaration instanceof InputDeclaration) {
+ InputDeclaration id = (InputDeclaration) declaration;
+ validateExpression(id.value, true);
+ }
+ }
+ return true;
+ }
+
+ /*
+ * One might also consider checking if the same variable is used with more than one type:
+ * .local $a = {$foo :number}
+ * .local $b = {$foo :string}
+ * .local $c = {$foo :datetime}
+ *
+ * But this is not necesarily an error.
+ * If $foo is a number, then it might be formatter as a number, or as date (epoch time),
+ * or something else.
+ *
+ * So it is not safe to complain. Especially with custom functions:
+ * # get the first name from a `Person` object
+ * .local $b = {$person :getField fieldName=firstName}
+ * # get formats a `Person` object
+ * .local $b = {$person :person}
+ */
+ private void validateExpression(Expression expression, boolean fromInput)
+ throws MFParseException {
+ String argName = null;
+ Annotation annotation = null;
+ if (expression instanceof Literal) {
+ // ...{foo}... or ...{|foo|}... or ...{123}...
+ // does not declare anything
+ } else if (expression instanceof LiteralExpression) {
+ LiteralExpression le = (LiteralExpression) expression;
+ argName = le.arg.value;
+ annotation = le.annotation;
+ } else if (expression instanceof VariableExpression) {
+ VariableExpression ve = (VariableExpression) expression;
+ // ...{$foo :bar opt1=|str| opt2=$x opt3=$y}...
+ // .input {$foo :number} => declares `foo`, if already declared is an error
+ // .local $a={$foo} => declares `a`, but only used `foo`, does not declare it
+ argName = ve.arg.name;
+ annotation = ve.annotation;
+ } else if (expression instanceof FunctionExpression) {
+ // ...{$foo :bar opt1=|str| opt2=$x opt3=$y}...
+ FunctionExpression fe = (FunctionExpression) expression;
+ annotation = fe.annotation;
+ }
+
+ if (annotation instanceof FunctionAnnotation) {
+ FunctionAnnotation fa = (FunctionAnnotation) annotation;
+ if (fa.options != null) {
+ for (Option opt : fa.options.values()) {
+ LiteralOrVariableRef val = opt.value;
+ if (val instanceof VariableRef) {
+ // We had something like {:f option=$val}, it means we's seen `val`
+ // It is not a declaration, so not an error.
+ addVariableDeclaration(((VariableRef) val).name);
+ }
+ }
+ }
+ }
+
+ // We chech the argument name after options to prevent errors like this:
+ // .local $foo = {$a :b option=$foo}
+ if (argName != null) {
+ // if we come from `.input {$foo :function}` then `varName` is null
+ // and `argName` is `foo`
+ if (fromInput) {
+ addVariableDeclaration(argName);
+ } else {
+ // Remember that we've seen it, to complain if there is a declaration later
+ declaredVars.add(argName);
+ }
+ }
+ }
+
+ private boolean addVariableDeclaration(String varName) throws MFParseException {
+ if (declaredVars.contains(varName)) {
+ error("Variable '" + varName + "' already declared");
+ return false;
+ }
+ declaredVars.add(varName);
+ return true;
+ }
+
+ private void error(String text) throws MFParseException {
+ throw new MFParseException(text, -1);
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/MFFunctionRegistry.java b/android_icu4j/src/main/java/android/icu/message2/MFFunctionRegistry.java
new file mode 100644
index 0000000..4269f09
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/MFFunctionRegistry.java
@@ -0,0 +1,349 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: https://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class is used to register mappings between various function
+ * names and the factories that can create those functions.
+ *
+ * <p>For example to add formatting for a {@code Person} object one would need to:</p>
+ * <ul>
+ * <li>write a function (class, lambda, etc.) that does the formatting proper
+ * (implementing {@link Formatter})</li>
+ * <li>write a factory that creates such a function
+ * (implementing {@link FormatterFactory})</li>
+ * <li>add a mapping from the function name as used in the syntax
+ * (for example {@code "person"}) to the factory</li>
+ * <li>optionally add a mapping from the class to format ({@code ...Person.class}) to
+ * the formatter name ({@code "person"}), so that one can use a placeholder in the message
+ * without specifying a function (for example {@code "... {$me} ..."} instead of
+ * {@code "... {$me :person} ..."}, if the class of {@code $me} is an {@code instanceof Person}).
+ * </li>
+ * </ul>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class MFFunctionRegistry {
+ private final Map<String, FormatterFactory> formattersMap;
+ private final Map<String, SelectorFactory> selectorsMap;
+ private final Map<Class<?>, String> classToFormatter;
+
+ private MFFunctionRegistry(Builder builder) {
+ this.formattersMap = new HashMap<>(builder.formattersMap);
+ this.selectorsMap = new HashMap<>(builder.selectorsMap);
+ this.classToFormatter = new HashMap<>(builder.classToFormatter);
+ }
+
+ /**
+ * Creates a builder.
+ *
+ * @return the Builder.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns the formatter factory used to create the formatter for function
+ * named {@code name}.
+ *
+ * <p>Note: function name here means the name used to refer to the function in the
+ * MessageFormat 2 syntax, for example {@code "... {$exp :datetime} ..."}<br>
+ * The function name here is {@code "datetime"}, and does not have to correspond to the
+ * name of the methods / classes used to implement the functionality.</p>
+ *
+ * <p>For example one might write a {@code PersonFormatterFactory} returning a {@code PersonFormatter},
+ * and map that to the MessageFormat function named {@code "person"}.<br>
+ * The only name visible to the users of MessageFormat syntax will be {@code "person"}.</p>
+ *
+ * @param formatterName the function name.
+ * @return the factory creating formatters for {@code name}. Returns {@code null} if none is registered.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public FormatterFactory getFormatter(String formatterName) {
+ return formattersMap.get(formatterName);
+ }
+
+ /**
+ * Get all know names that have a mappings from name to {@link FormatterFactory}.
+ *
+ * @return a set of all the known formatter names.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Set<String> getFormatterNames() {
+ return formattersMap.keySet();
+ }
+
+ /**
+ * Returns the name of the formatter used to format an object of type {@code clazz}.
+ *
+ * @param clazz the class of the object to format.
+ * @return the name of the formatter class, if registered. Returns {@code null} otherwise.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public String getDefaultFormatterNameForType(Class<?> clazz) {
+ // Search for the class "as is", to save time.
+ // If we don't find it then we iterate the registered classes and check
+ // if the class is an instanceof the ones registered.
+ // For example a BuddhistCalendar when we only registered Calendar
+ String result = classToFormatter.get(clazz);
+ if (result != null) {
+ return result;
+ }
+ // We didn't find the class registered explicitly "as is"
+ for (Map.Entry<Class<?>, String> e : classToFormatter.entrySet()) {
+ if (e.getKey().isAssignableFrom(clazz)) {
+ return e.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get all know classes that have a mappings from class to function name.
+ *
+ * @return a set of all the known classes that have mapping to function names.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Set<Class<?>> getDefaultFormatterTypes() {
+ return classToFormatter.keySet();
+ }
+
+ /**
+ * Returns the selector factory used to create the selector for function
+ * named {@code name}.
+ *
+ * <p>Note: the same comments about naming as the ones on {@code getFormatter} apply.</p>
+ *
+ * @param selectorName the selector name.
+ * @return the factory creating selectors for {@code name}. Returns {@code null} if none is registered.
+ * @see #getFormatter(String)
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public SelectorFactory getSelector(String selectorName) {
+ return selectorsMap.get(selectorName);
+ }
+
+ /**
+ * Get all know names that have a mappings from name to {@link SelectorFactory}.
+ *
+ * @return a set of all the known selector names.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Set<String> getSelectorNames() {
+ return selectorsMap.keySet();
+ }
+
+ /**
+ * A {@code Builder} used to build instances of {@link MFFunctionRegistry}.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static class Builder {
+ private final Map<String, FormatterFactory> formattersMap = new HashMap<>();
+ private final Map<String, SelectorFactory> selectorsMap = new HashMap<>();
+ private final Map<Class<?>, String> classToFormatter = new HashMap<>();
+
+ // Prevent direct creation
+ private Builder() {}
+
+ /**
+ * Adds all the mapping from another registry to this one.
+ *
+ * @param functionRegistry the registry to copy from.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder addAll(MFFunctionRegistry functionRegistry) {
+ formattersMap.putAll(functionRegistry.formattersMap);
+ selectorsMap.putAll(functionRegistry.selectorsMap);
+ classToFormatter.putAll(functionRegistry.classToFormatter);
+ return this;
+ }
+
+ /**
+ * Adds a mapping from a formatter name to a {@link FormatterFactory}.
+ *
+ * @param formatterName the function name (as used in the MessageFormat 2 syntax).
+ * @param formatterFactory the factory that handles the name.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setFormatter(String formatterName, FormatterFactory formatterFactory) {
+ formattersMap.put(formatterName, formatterFactory);
+ return this;
+ }
+
+ /**
+ * Remove the formatter associated with the name.
+ *
+ * @param formatterName the name of the formatter to remove.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder removeFormatter(String formatterName) {
+ formattersMap.remove(formatterName);
+ return this;
+ }
+
+ /**
+ * Remove all the formatter mappings.
+ *
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder clearFormatters() {
+ formattersMap.clear();
+ return this;
+ }
+
+ /**
+ * Adds a mapping from a type to format to a {@link FormatterFactory} formatter name.
+ *
+ * @param clazz the class of the type to format.
+ * @param formatterName the formatter name (as used in the MessageFormat 2 syntax).
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setDefaultFormatterNameForType(Class<?> clazz, String formatterName) {
+ classToFormatter.put(clazz, formatterName);
+ return this;
+ }
+
+ /**
+ * Remove the function name associated with the class.
+ *
+ * @param clazz the class to remove the mapping for.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder removeDefaultFormatterNameForType(Class<?> clazz) {
+ classToFormatter.remove(clazz);
+ return this;
+ }
+
+ /**
+ * Remove all the class to formatter-names mappings.
+ *
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder clearDefaultFormatterNames() {
+ classToFormatter.clear();
+ return this;
+ }
+
+ /**
+ * Adds a mapping from a selector name to a {@link SelectorFactory}.
+ *
+ * @param selectorName the function name (as used in the MessageFormat 2 syntax).
+ * @param selectorFactory the factory that handles the name.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder setSelector(String selectorName, SelectorFactory selectorFactory) {
+ selectorsMap.put(selectorName, selectorFactory);
+ return this;
+ }
+
+ /**
+ * Remove the selector associated with the name.
+ *
+ * @param selectorName the name of the selector to remove.
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder removeSelector(String selectorName) {
+ selectorsMap.remove(selectorName);
+ return this;
+ }
+
+ /**
+ * Remove all the selector mappings.
+ *
+ * @return the builder, for fluent use.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public Builder clearSelectors() {
+ selectorsMap.clear();
+ return this;
+ }
+
+ /**
+ * Builds an instance of {@link MFFunctionRegistry}.
+ *
+ * @return the function registry created.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public MFFunctionRegistry build() {
+ return new MFFunctionRegistry(this);
+ }
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/MFParseException.java b/android_icu4j/src/main/java/android/icu/message2/MFParseException.java
new file mode 100644
index 0000000..bac04c3
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/MFParseException.java
@@ -0,0 +1,34 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2024 and later: Unicode, Inc. and others.
+// License & terms of use: https://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.text.ParseException;
+
+/**
+ * Used to report parsing errors in {@link MessageFormatter}.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class MFParseException extends ParseException {
+ private static final long serialVersionUID = -7634219305388292407L;
+
+ /**
+ * Constructs a MFParseException with the specified message and offset.
+ *
+ * @param message the message
+ * @param errorOffset the position where the error is found while parsing.
+ */
+ public MFParseException(String message, int errorOffset) {
+ super(message, errorOffset);
+ }
+
+ @Override
+ public String getMessage() {
+ return super.getMessage();
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/MFParser.java b/android_icu4j/src/main/java/android/icu/message2/MFParser.java
new file mode 100644
index 0000000..efa7b77
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/MFParser.java
@@ -0,0 +1,789 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: https://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class parses a {@code MessageFormat 2} syntax into a data model {@link MFDataModel.Message}.
+ *
+ * <p>It is used by {@link MessageFormatter}, but it might be handy for various tools.</p>
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class MFParser {
+ private static final int EOF = -1;
+ private final InputSource input;
+
+ MFParser(String text) {
+ this.input = new InputSource(text);
+ }
+
+ /**
+ * Parses a {@code MessageFormat 2} syntax into a {@link MFDataModel.Message}.
+ *
+ * <p>It is used by {@link MessageFormatter}, but it might be handy for various tools.</p>
+ * @param input the text to parse
+ * @return the parsed {@code MFDataModel.Message}
+ * @throws MFParseException if errors are detected
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static MFDataModel.Message parse(String input) throws MFParseException {
+ return new MFParser(input).parseImpl();
+ }
+
+ // Parser proper
+ private MFDataModel.Message parseImpl() throws MFParseException {
+ MFDataModel.Message result;
+ int cp = input.peekChar();
+ if (cp == '.') { // declarations or .match
+ result = getComplexMessage();
+ } else if (cp == '{') { // `{` or `{{`
+ cp = input.readCodePoint();
+ cp = input.peekChar();
+ if (cp == '{') { // `{{`, complex body without declarations
+ input.backup(1); // let complexBody deal with the wrapping {{ and }}
+ MFDataModel.Pattern pattern = getQuotedPattern();
+ result = new MFDataModel.PatternMessage(new ArrayList<>(), pattern);
+ } else { // placeholder
+ input.backup(1); // We want the '{' present, to detect the part as placeholder.
+ MFDataModel.Pattern pattern = getPattern();
+ result = new MFDataModel.PatternMessage(new ArrayList<>(), pattern);
+ }
+ } else {
+ MFDataModel.Pattern pattern = getPattern();
+ result = new MFDataModel.PatternMessage(new ArrayList<>(), pattern);
+ }
+ skipOptionalWhitespaces();
+ checkCondition(input.atEnd(), "Content detected after the end of the message.");
+ new MFDataModelValidator(result).validate();
+ return result;
+ }
+
+ // abnf: simple-message = [simple-start pattern]
+ // abnf: simple-start = simple-start-char / text-escape / placeholder
+ // abnf: pattern = *(text-char / text-escape / placeholder)
+ private MFDataModel.Pattern getPattern() throws MFParseException {
+ MFDataModel.Pattern pattern = new MFDataModel.Pattern();
+ while (true) {
+ MFDataModel.PatternPart part = getPatternPart();
+ if (part == null) {
+ break;
+ }
+ pattern.parts.add(part);
+ }
+ // checkCondition(!pattern.parts.isEmpty(), "Empty pattern");
+ return pattern;
+ }
+
+ private MFDataModel.PatternPart getPatternPart() throws MFParseException {
+ int cp = input.peekChar();
+ switch (cp) {
+ case EOF:
+ return null;
+ case '}': // This is the end, otherwise it would be escaped
+ return null;
+ case '{':
+ MFDataModel.Expression ph = getPlaceholder();
+ return ph;
+ default:
+ String plainText = getText();
+ MFDataModel.StringPart sp = new MFDataModel.StringPart(plainText);
+ return sp;
+ }
+ }
+
+ private String getText() {
+ StringBuilder result = new StringBuilder();
+ while (true) {
+ int cp = input.readCodePoint();
+ switch (cp) {
+ case EOF:
+ return result.toString();
+ case '\\':
+ cp = input.readCodePoint();
+ if (cp == '\\' || cp == '{' || cp == '|' | cp == '}') {
+ result.appendCodePoint(cp);
+ } else { // TODO: Error, treat invalid escape?
+ result.appendCodePoint('\\');
+ result.appendCodePoint(cp);
+ }
+ break;
+ case '.':
+ case '@':
+ case '|':
+ result.appendCodePoint(cp);
+ break;
+ default:
+ if (StringUtils.isContentChar(cp) || StringUtils.isWhitespace(cp)) {
+ result.appendCodePoint(cp);
+ } else {
+ input.backup(1);
+ return result.toString();
+ }
+ }
+ }
+ }
+
+ // abnf: placeholder = expression / markup
+ // abnf: expression = literal-expression
+ // abnf: / variable-expression
+ // abnf: / annotation-expression
+ // abnf: literal-expression = "{" [s] literal [s annotation] *(s attribute) [s] "}"
+ // abnf: variable-expression = "{" [s] variable [s annotation] *(s attribute) [s] "}"
+ // abnf: annotation-expression = "{" [s] annotation *(s attribute) [s] "}"
+ // abnf: markup = "{" [s] "#" identifier *(s option) *(s attribute) [s] ["/"] "}" ; open and standalone
+ // abnf: / "{" [s] "/" identifier *(s option) *(s attribute) [s] "}" ; close
+ private MFDataModel.Expression getPlaceholder() throws MFParseException {
+ int cp = input.peekChar();
+ if (cp != '{') {
+ return null;
+ }
+ input.readCodePoint(); // consume the '{'
+ skipOptionalWhitespaces();
+ cp = input.peekChar();
+
+ MFDataModel.Expression result;
+ if (cp == '#' || cp == '/') {
+ result = getMarkup();
+ } else if (cp == '$') {
+ result = getVariableExpression();
+ } else if (StringUtils.isFunctionSigil(cp)
+ || StringUtils.isPrivateAnnotationSigil(cp)
+ || StringUtils.isReservedAnnotationSigil(cp)) {
+ result = getAnnotationExpression();
+ } else {
+ result = getLiteralExpression();
+ }
+
+ skipOptionalWhitespaces();
+ cp = input.readCodePoint(); // consume the '}'
+ checkCondition(cp == '}', "Unclosed placeholder");
+
+ return result;
+ }
+
+ private MFDataModel.Annotation getAnnotation() throws MFParseException {
+ int position = input.getPosition();
+ skipOptionalWhitespaces();
+
+ int cp = input.peekChar();
+ switch (cp) {
+ case '}':
+ return null;
+ case ':': // annotation, function
+ // abnf: function = ":" identifier *(s option)
+ input.readCodePoint(); // Consume the sigil
+ String identifier = getIdentifier();
+ checkCondition(identifier != null, "Annotation / function name missing");
+ Map<String, MFDataModel.Option> options = getOptions();
+ return new MFDataModel.FunctionAnnotation(identifier, options);
+ default: // reserved && private
+ if (StringUtils.isReservedAnnotationSigil(cp)
+ || StringUtils.isPrivateAnnotationSigil(cp)) {
+ cp = input.readCodePoint();
+ // The sigil is part of the body.
+ // Safe to cast to char, the code point is in BMP
+ identifier = (char) cp + getIdentifier();
+ String body = getReservedBody();
+ return new MFDataModel.UnsupportedAnnotation(identifier + body);
+ }
+ }
+ input.gotoPosition(position);
+ return null;
+ }
+
+ private MFDataModel.Annotation getMarkupAnnotation() throws MFParseException {
+ skipOptionalWhitespaces();
+
+ int cp = input.peekChar();
+ switch (cp) {
+ case '}':
+ return null;
+ case '#':
+ case '/':
+ // abnf: markup = "{" [s] "#" identifier *(s option) *(s attribute) [s] ["/"] "}" ; open and standalone
+ // abnf: / "{" [s] "/" identifier *(s option) *(s attribute) [s] "}" ; close
+ input.readCodePoint(); // Consume the sigil
+ String identifier = getIdentifier();
+ checkCondition(identifier != null, "Annotation / function name missing");
+ Map<String, MFDataModel.Option> options = getOptions();
+ return new MFDataModel.FunctionAnnotation(identifier, options);
+ default:
+ // reserved, private, function, something else,
+ return null;
+ }
+ }
+
+ // abnf: literal-expression = "{" [s] literal [s annotation] *(s attribute) [s] "}"
+ private MFDataModel.Expression getLiteralExpression() throws MFParseException {
+ MFDataModel.Literal literal = getLiteral();
+ checkCondition(literal != null, "Literal expression expected.");
+
+ MFDataModel.Annotation annotation = null;
+ int wsCount = skipWhitespaces();
+ if (wsCount > 0) { // we might have an annotation
+ annotation = getAnnotation();
+ if (annotation == null) {
+ // We had some spaces, but no annotation.
+ // So we put (some) back for the possible attributes.
+ input.backup(1);
+ }
+ }
+
+ List<MFDataModel.Attribute> attributes = getAttributes();
+
+ // Literal without a function, for example {|hello|} or {123}
+ return new MFDataModel.LiteralExpression(literal, annotation, attributes);
+ }
+
+ // abnf: variable-expression = "{" [s] variable [s annotation] *(s attribute) [s] "}"
+ private MFDataModel.VariableExpression getVariableExpression() throws MFParseException {
+ MFDataModel.VariableRef variableRef = getVariableRef();
+ MFDataModel.Annotation annotation = getAnnotation();
+ List<MFDataModel.Attribute> attributes = getAttributes();
+ // Variable without a function, for example {$foo}
+ return new MFDataModel.VariableExpression(variableRef, annotation, attributes);
+ }
+
+ // abnf: annotation-expression = "{" [s] annotation *(s attribute) [s] "}"
+ private MFDataModel.Expression getAnnotationExpression() throws MFParseException {
+ MFDataModel.Annotation annotation = getAnnotation();
+ List<MFDataModel.Attribute> attributes = getAttributes();
+
+ if (annotation instanceof MFDataModel.FunctionAnnotation) {
+ return new MFDataModel.FunctionExpression(
+ (MFDataModel.FunctionAnnotation) annotation, attributes);
+ } else if (annotation instanceof MFDataModel.UnsupportedAnnotation) {
+ return new MFDataModel.UnsupportedExpression(
+ (MFDataModel.UnsupportedAnnotation) annotation, attributes);
+ } else {
+ error("Unexpected annotation : " + annotation);
+ }
+ return null;
+ }
+
+ // abnf: markup = "{" [s] "#" identifier *(s option) *(s attribute) [s] ["/"] "}" ; open and standalone
+ // abnf: / "{" [s] "/" identifier *(s option) *(s attribute) [s] "}" ; close
+ private MFDataModel.Markup getMarkup() throws MFParseException {
+ int cp = input.peekChar(); // consume the '{'
+ checkCondition(cp == '#' || cp == '/', "Should not happen. Expecting a markup.");
+
+ MFDataModel.Markup.Kind kind =
+ cp == '/' ? MFDataModel.Markup.Kind.CLOSE : MFDataModel.Markup.Kind.OPEN;
+
+ MFDataModel.Annotation annotation = getMarkupAnnotation();
+ List<MFDataModel.Attribute> attributes = getAttributes();
+
+ cp = input.peekChar();
+ if (cp == '/') {
+ kind = MFDataModel.Markup.Kind.STANDALONE;
+ input.readCodePoint();
+ }
+
+ if (annotation instanceof MFDataModel.FunctionAnnotation) {
+ MFDataModel.FunctionAnnotation fa = (MFDataModel.FunctionAnnotation) annotation;
+ return new MFDataModel.Markup(kind, fa.name, fa.options, attributes);
+ }
+
+ return null;
+ }
+
+ private List<MFDataModel.Attribute> getAttributes() throws MFParseException {
+ List<MFDataModel.Attribute> result = new ArrayList<>();
+ while (true) {
+ MFDataModel.Attribute attribute = getAttribute();
+ if (attribute == null) {
+ break;
+ }
+ result.add(attribute);
+ }
+ return result;
+ }
+
+ // abnf: attribute = "@" identifier [[s] "=" [s] (literal / variable)]
+ private MFDataModel.Attribute getAttribute() throws MFParseException {
+ int position = input.getPosition();
+ if (skipWhitespaces() == 0) {
+ input.gotoPosition(position);
+ return null;
+ }
+ int cp = input.peekChar();
+ if (cp == '@') {
+ input.readCodePoint(); // consume the '@'
+ String id = getIdentifier();
+ int wsCount = skipWhitespaces();
+ cp = input.peekChar();
+ MFDataModel.LiteralOrVariableRef literalOrVariable = null;
+ if (cp == '=') {
+ input.readCodePoint();
+ skipOptionalWhitespaces();
+ literalOrVariable = getLiteralOrVariableRef();
+ checkCondition(literalOrVariable != null, "Attributes must have a value after `=`");
+ } else {
+ // was not equal, attribute without a value, put the "spaces" back.
+ input.backup(wsCount);
+ }
+ return new MFDataModel.Attribute(id, literalOrVariable);
+ } else {
+ input.gotoPosition(position);
+ }
+ return null;
+ }
+
+ // abnf: reserved-body = *([s] 1*(reserved-char / reserved-escape / quoted))
+ // abnf: reserved-escape = backslash ( backslash / "{" / "|" / "}" )
+ private String getReservedBody() throws MFParseException {
+ int spaceCount = skipWhitespaces();
+ StringBuilder result = new StringBuilder();
+ while (true) {
+ int cp = input.readCodePoint();
+ if (StringUtils.isReservedChar(cp)) {
+ result.appendCodePoint(cp);
+ } else if (cp == '\\') {
+ cp = input.readCodePoint();
+ checkCondition(
+ cp == '{' || cp == '|' || cp == '}',
+ "Invalid escape sequence. Only \\{, \\| and \\} are valid here.");
+ result.append(cp);
+ } else if (cp == '|') {
+ input.backup(1);
+ MFDataModel.Literal quoted = getQuotedLiteral();
+ result.append(quoted.value);
+ } else if (cp == EOF) {
+ return result.toString();
+ } else {
+ if (result.length() == 0) {
+ input.backup(spaceCount + 1);
+ return "";
+ } else {
+ input.backup(1);
+ return result.toString();
+ }
+ }
+ }
+ }
+
+ // abnf: identifier = [namespace ":"] name
+ // abnf: namespace = name
+ // abnf: name = name-start *name-char
+ private String getIdentifier() throws MFParseException {
+ String namespace = getName();
+ if (namespace == null) {
+ return null;
+ }
+ int cp = input.readCodePoint();
+ if (cp == ':') { // the previous name was namespace
+ String name = getName();
+ checkCondition(name != null, "Expected name after namespace '" + namespace + "'");
+ return namespace + ":" + name;
+ } else {
+ input.backup(1);
+ }
+ return namespace;
+ }
+
+ // abnf helper: *(s option)
+ private Map<String, MFDataModel.Option> getOptions() throws MFParseException {
+ Map<String, MFDataModel.Option> options = new LinkedHashMap<>();
+ while (true) {
+ MFDataModel.Option option = getOption();
+ if (option == null) {
+ break;
+ }
+ if (options.containsKey(option.name)) {
+ error("Duplicated option '" + option.name + "'");
+ }
+ options.put(option.name, option);
+ }
+ return options;
+ }
+
+ // abnf: option = identifier [s] "=" [s] (literal / variable)
+ private MFDataModel.Option getOption() throws MFParseException {
+ int position = input.getPosition();
+ skipOptionalWhitespaces();
+ String identifier = getIdentifier();
+ if (identifier == null) {
+ input.gotoPosition(position);
+ return null;
+ }
+ skipOptionalWhitespaces();
+ int cp = input.readCodePoint();
+ checkCondition(cp == '=', "Expected '='");
+ // skipOptionalWhitespaces();
+ MFDataModel.LiteralOrVariableRef litOrVar = getLiteralOrVariableRef();
+ if (litOrVar == null) {
+ error("Options must have a value. An empty string should be quoted.");
+ }
+ return new MFDataModel.Option(identifier, litOrVar);
+ }
+
+ private MFDataModel.LiteralOrVariableRef getLiteralOrVariableRef() throws MFParseException {
+ int cp = input.peekChar();
+ if (cp == '$') {
+ return getVariableRef();
+ }
+ return getLiteral();
+ }
+
+ // abnf: literal = quoted / unquoted
+ private MFDataModel.Literal getLiteral() throws MFParseException {
+ int cp = input.readCodePoint();
+ switch (cp) {
+ case '|': // quoted
+ // abnf: quoted = "|" *(quoted-char / quoted-escape) "|"
+ input.backup(1);
+ MFDataModel.Literal ql = getQuotedLiteral();
+ return ql;
+ default: // unquoted
+ input.backup(1);
+ MFDataModel.Literal unql = getUnQuotedLiteral();
+ return unql;
+ }
+ }
+
+ private MFDataModel.VariableRef getVariableRef() throws MFParseException {
+ int cp = input.readCodePoint();
+ if (cp != '$') {
+ checkCondition(cp == '$', "We can't get here");
+ }
+
+ // abnf: variable = "$" name
+ String name = getName();
+ checkCondition(name != null, "Invalid variable reference following $");
+ return new MFDataModel.VariableRef(name);
+ }
+
+ private MFDataModel.Literal getQuotedLiteral() throws MFParseException {
+ StringBuilder result = new StringBuilder();
+ int cp = input.readCodePoint();
+ checkCondition(cp == '|', "expected starting '|'");
+ while (true) {
+ cp = input.readCodePoint();
+ if (cp == EOF) {
+ break;
+ } else if (StringUtils.isQuotedChar(cp)) {
+ result.appendCodePoint(cp);
+ } else if (cp == '\\') {
+ cp = input.readCodePoint();
+ checkCondition(cp == '|', "Invalid escape sequence, only \"\\|\" is valid here");
+ result.appendCodePoint('|');
+ } else {
+ break;
+ }
+ }
+
+ checkCondition(cp == '|', "expected ending '|'");
+
+ return new MFDataModel.Literal(result.toString());
+ }
+
+ private MFDataModel.Literal getUnQuotedLiteral() throws MFParseException {
+ String name = getName();
+ if (name != null) {
+ return new MFDataModel.Literal(name);
+ }
+ return getNumberLiteral();
+ }
+
+ // abnf: ; number-literal matches JSON number (https://www.rfc-editor.org/rfc/rfc8259#section-6)
+ // abnf: number-literal = ["-"] (%x30 / (%x31-39 *DIGIT)) ["." 1*DIGIT] [%i"e" ["-" / "+"] 1*DIGIT]
+ private static final Pattern RE_NUMBER_LITERAL =
+ Pattern.compile("^-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+\\-]?[0-9]+)?");
+
+ private MFDataModel.Literal getNumberLiteral() {
+ String numberString = peekWithRegExp(RE_NUMBER_LITERAL);
+ if (numberString != null) {
+ return new MFDataModel.Literal(numberString);
+ }
+ return null;
+ }
+
+ private void skipMandatoryWhitespaces() throws MFParseException {
+ int count = skipWhitespaces();
+ checkCondition(count > 0, "Space expected");
+ }
+
+ private void skipOptionalWhitespaces() {
+ skipWhitespaces();
+ }
+
+ private int skipWhitespaces() {
+ int skipCount = 0;
+ while (true) {
+ int cp = input.readCodePoint();
+ if (cp == EOF) {
+ return skipCount;
+ }
+ if (!StringUtils.isWhitespace(cp)) {
+ input.backup(1);
+ return skipCount;
+ }
+ skipCount++;
+ }
+ }
+
+ private MFDataModel.Message getComplexMessage() throws MFParseException {
+ List<MFDataModel.Declaration> declarations = new ArrayList<>();
+ boolean foundMatch = false;
+ while (true) {
+ MFDataModel.Declaration declaration = getDeclaration();
+ if (declaration == null) {
+ break;
+ }
+ if (declaration instanceof MatchDeclaration) {
+ foundMatch = true;
+ break;
+ }
+ declarations.add(declaration);
+ }
+ if (foundMatch) {
+ return getMatch(declarations);
+ } else { // Expect {{...}} or end of message
+ skipOptionalWhitespaces();
+ int cp = input.peekChar();
+ if (cp == EOF) {
+ // Only declarations, no pattern
+ return new MFDataModel.PatternMessage(declarations, null);
+ } else {
+ MFDataModel.Pattern pattern = getQuotedPattern();
+ return new MFDataModel.PatternMessage(declarations, pattern);
+ }
+ }
+ }
+
+ // abnf: matcher = match-statement 1*([s] variant)
+ // abnf: match-statement = match 1*([s] selector)
+ // abnf: selector = expression
+ // abnf: variant = key *(s key) [s] quoted-pattern
+ // abnf: key = literal / "*"
+ // abnf: match = %s".match"
+ private MFDataModel.SelectMessage getMatch(List<MFDataModel.Declaration> declarations)
+ throws MFParseException {
+ // ".match" was already consumed by the caller
+ // Look for selectors
+ List<MFDataModel.Expression> expressions = new ArrayList<>();
+ while (true) {
+ skipMandatoryWhitespaces();
+ MFDataModel.Expression expression = getPlaceholder();
+ if (expression == null) {
+ break;
+ }
+ checkCondition(
+ !(expression instanceof MFDataModel.Markup), "Cannot do selection on markup");
+ expressions.add(expression);
+ }
+
+ checkCondition(!expressions.isEmpty(), "There should be at least one selector expression.");
+
+ // At this point we need to look for variants, which are key - value
+ List<MFDataModel.Variant> variants = new ArrayList<>();
+ while (true) {
+ MFDataModel.Variant variant = getVariant();
+ if (variant == null) {
+ break;
+ }
+ variants.add(variant);
+ }
+ return new MFDataModel.SelectMessage(declarations, expressions, variants);
+ }
+
+ // abnf: variant = key *(s key) [s] quoted-pattern
+ // abnf: key = literal / "*"
+ private MFDataModel.Variant getVariant() throws MFParseException {
+ List<MFDataModel.LiteralOrCatchallKey> keys = new ArrayList<>();
+ // abnf variant = key *(s key) [s] quoted-pattern
+ while (true) {
+ // Space is required between keys
+ MFDataModel.LiteralOrCatchallKey key = getKey(!keys.isEmpty());
+ if (key == null) {
+ break;
+ }
+ keys.add(key);
+ }
+ skipOptionalWhitespaces();
+ if (input.atEnd()) {
+ checkCondition(
+ keys.isEmpty(), "After selector keys it is mandatory to have a pattern.");
+ return null;
+ }
+ MFDataModel.Pattern pattern = getQuotedPattern();
+ return new MFDataModel.Variant(keys, pattern);
+ }
+
+ private MFDataModel.LiteralOrCatchallKey getKey(boolean requireSpaces) throws MFParseException {
+ if (requireSpaces) {
+ skipMandatoryWhitespaces();
+ } else {
+ skipOptionalWhitespaces();
+ }
+ int cp = input.peekChar();
+ if (cp == '*') {
+ input.readCodePoint(); // consume the '*'
+ return new MFDataModel.CatchallKey();
+ }
+ if (cp == EOF) {
+ return null;
+ }
+ return getLiteral();
+ }
+
+ private static class MatchDeclaration implements MFDataModel.Declaration {
+ // Provides a common type that extends MFDataModel.Declaration but for match.
+ // There is no such thing in the data model.
+ }
+
+ // abnf: input-declaration = input [s] variable-expression
+ // abnf: local-declaration = local s variable [s] "=" [s] expression
+ // abnf: reserved-statement = reserved-keyword [s reserved-body] 1*([s] expression)
+ // abnf: reserved-keyword = "." name
+ private MFDataModel.Declaration getDeclaration() throws MFParseException {
+ int position = input.getPosition();
+ skipOptionalWhitespaces();
+ int cp = input.readCodePoint();
+ if (cp != '.') {
+ input.gotoPosition(position);
+ return null;
+ }
+ String declName = getName();
+ checkCondition(declName != null, "Expected a declaration after the '.'");
+
+ MFDataModel.Expression expression;
+ switch (declName) {
+ case "input":
+ skipMandatoryWhitespaces();
+ expression = getPlaceholder();
+ String inputVarName = null;
+ if (expression instanceof MFDataModel.VariableExpression) {
+ inputVarName = ((MFDataModel.VariableExpression) expression).arg.name;
+ }
+ if (expression instanceof MFDataModel.VariableExpression) {
+ return new MFDataModel.InputDeclaration(
+ inputVarName, (MFDataModel.VariableExpression) expression);
+ }
+ break;
+ case "local":
+ // abnf: local-declaration = local s variable [s] "=" [s] expression
+ skipMandatoryWhitespaces();
+ MFDataModel.LiteralOrVariableRef varName = getVariableRef();
+ skipOptionalWhitespaces();
+ cp = input.readCodePoint();
+ checkCondition(cp == '=', declName);
+ skipOptionalWhitespaces();
+ expression = getPlaceholder();
+ if (varName instanceof MFDataModel.VariableRef) {
+ return new MFDataModel.LocalDeclaration(
+ ((MFDataModel.VariableRef) varName).name, expression);
+ }
+ break;
+ case "match":
+ return new MatchDeclaration();
+ default: // abnf: reserved-statement = reserved-keyword [s reserved-body] 1*([s] expression)
+ skipOptionalWhitespaces();
+ String body = getReservedBody();
+ List<MFDataModel.Expression> expressions = new ArrayList<>();
+ while (true) {
+ skipOptionalWhitespaces();
+ expression = getPlaceholder();
+ // This also covers != null
+ if (expression instanceof MFDataModel.VariableExpression) {
+ expressions.add(expression);
+ } else {
+ break;
+ }
+ }
+ return new MFDataModel.UnsupportedStatement(declName, body, expressions);
+ }
+ return null;
+ }
+
+ // quoted-pattern = "{{" pattern "}}"
+ private MFDataModel.Pattern getQuotedPattern() throws MFParseException { // {{ ... }}
+ int cp = input.readCodePoint();
+ checkCondition(cp == '{', "Expected { for a complex body");
+ cp = input.readCodePoint();
+ checkCondition(cp == '{', "Expected second { for a complex body");
+ MFDataModel.Pattern pattern = getPattern();
+ cp = input.readCodePoint();
+ checkCondition(cp == '}', "Expected } to end a complex body");
+ cp = input.readCodePoint();
+ checkCondition(cp == '}', "Expected second } to end a complex body");
+ return pattern;
+ }
+
+ private String getName() throws MFParseException {
+ StringBuilder result = new StringBuilder();
+ int cp = input.readCodePoint();
+ checkCondition(cp != EOF, "Expected name or namespace.");
+ if (!StringUtils.isNameStart(cp)) {
+ input.backup(1);
+ return null;
+ }
+ result.appendCodePoint(cp);
+ while (true) {
+ cp = input.readCodePoint();
+ if (StringUtils.isNameChar(cp)) {
+ result.appendCodePoint(cp);
+ } else if (cp == EOF) {
+ break;
+ } else {
+ input.backup(1);
+ break;
+ }
+ }
+ return result.toString();
+ }
+
+ private void checkCondition(boolean condition, String message) throws MFParseException {
+ if (!condition) {
+ error(message);
+ }
+ }
+
+ private void error(String message) throws MFParseException {
+ StringBuilder finalMsg = new StringBuilder();
+ if (input == null) {
+ finalMsg.append("Parse error: ");
+ finalMsg.append(message);
+ } else {
+ int position = input.getPosition();
+ finalMsg.append("Parse error [" + input.getPosition() + "]: ");
+ finalMsg.append(message);
+ finalMsg.append("\n");
+ if (position != EOF) {
+ finalMsg.append(input.buffer.substring(0, position));
+ finalMsg.append("^^^");
+ finalMsg.append(input.buffer.substring(position));
+ } else {
+ finalMsg.append(input.buffer);
+ finalMsg.append("^^^");
+ }
+ }
+ throw new MFParseException(finalMsg.toString(), input.getPosition());
+ }
+
+ private String peekWithRegExp(Pattern pattern) {
+ StringView sv = new StringView(input.buffer, input.getPosition());
+ Matcher m = pattern.matcher(sv);
+ boolean found = m.find();
+ if (found) {
+ input.skip(m.group().length());
+ return m.group();
+ }
+ return null;
+ }
+}
diff --git a/android_icu4j/src/main/java/android/icu/message2/MFSerializer.java b/android_icu4j/src/main/java/android/icu/message2/MFSerializer.java
new file mode 100644
index 0000000..e207999
--- /dev/null
+++ b/android_icu4j/src/main/java/android/icu/message2/MFSerializer.java
@@ -0,0 +1,382 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+// © 2022 and later: Unicode, Inc. and others.
+// License & terms of use: https://www.unicode.org/copyright.html
+
+package android.icu.message2;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+
+import android.icu.message2.MFDataModel.Annotation;
+import android.icu.message2.MFDataModel.Attribute;
+import android.icu.message2.MFDataModel.CatchallKey;
+import android.icu.message2.MFDataModel.Declaration;
+import android.icu.message2.MFDataModel.Expression;
+import android.icu.message2.MFDataModel.FunctionAnnotation;
+import android.icu.message2.MFDataModel.FunctionExpression;
+import android.icu.message2.MFDataModel.InputDeclaration;
+import android.icu.message2.MFDataModel.Literal;
+import android.icu.message2.MFDataModel.LiteralExpression;
+import android.icu.message2.MFDataModel.LiteralOrCatchallKey;
+import android.icu.message2.MFDataModel.LiteralOrVariableRef;
+import android.icu.message2.MFDataModel.LocalDeclaration;
+import android.icu.message2.MFDataModel.Markup;
+import android.icu.message2.MFDataModel.Option;
+import android.icu.message2.MFDataModel.Pattern;
+import android.icu.message2.MFDataModel.PatternMessage;
+import android.icu.message2.MFDataModel.PatternPart;
+import android.icu.message2.MFDataModel.SelectMessage;
+import android.icu.message2.MFDataModel.StringPart;
+import android.icu.message2.MFDataModel.UnsupportedAnnotation;
+import android.icu.message2.MFDataModel.UnsupportedExpression;
+import android.icu.message2.MFDataModel.UnsupportedStatement;
+import android.icu.message2.MFDataModel.VariableExpression;
+import android.icu.message2.MFDataModel.VariableRef;
+import android.icu.message2.MFDataModel.Variant;
+
+/**
+ * This class serializes a MessageFormat 2 data model {@link MFDataModel.Message} to a string,
+ * with the proper MessageFormat 2 syntax.
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide Only a subset of ICU is exposed in Android
+ * @hide draft / provisional / internal are hidden on Android
+ */
+@Deprecated
+public class MFSerializer {
+ private boolean shouldDoubleQuotePattern = false;
+ private boolean needSpace = false;
+ private final StringBuilder result = new StringBuilder();
+
+ /**
+ * Method converting the {@link MFDataModel.Message} to a string in MessageFormat 2 syntax.
+ *
+ * <p>The result is not necessarily identical with the original string parsed to generate
+ * the data model. But is is functionally equivalent.</p>
+ *
+ * @param message the data model message to serialize
+ * @return the serialized message, in MessageFormat 2 syntax
+ *
+ * @deprecated This API is for technology preview only.
+ * @hide draft / provisional / internal are hidden on Android
+ */
+ @Deprecated
+ public static String dataModelToString(MFDataModel.Message message) {
+ return new MFSerializer().messageToString(message);
+ }
+
+ private String messageToString(MFDataModel.Message message) {
+ if (message instanceof PatternMessage) {
+ patternMessageToString((PatternMessage) message);
+ } else if (message instanceof SelectMessage) {
+ selectMessageToString((SelectMessage) message);
+ } else {
+ errorType("Message", message);
+ }
+ return result.toString();
+ }
+
+ private void selectMessageToString(SelectMessage message) {
+ declarationsToString(message.declarations);
+ shouldDoubleQuotePattern = true;
+ addSpaceIfNeeded();
+ result.append(".match");
+ for (Expression selector : message.selectors) {
+ result.append(' ');
+ expressionToString(selector);
+ }
+ for (Variant variant : message.variants) {
+ variantToString(variant);
+ }
+ }
+
+ private void patternMessageToString(PatternMessage message) {
+ declarationsToString(message.declarations);
+ patternToString(message.pattern);
+ }
+
+ private void patternToString(Pattern pattern) {
+ addSpaceIfNeeded();
+ if (shouldDoubleQuotePattern) {
+ result.append("{{");
+ }
+ for (PatternPart part : pattern.parts) {
+ if (part instanceof StringPart) {
+ stringPartToString((StringPart) part);
+ } else {
+ expressionToString((Expression) part);
+ }
+ }
+ if (shouldDoubleQuotePattern) {
+ result.append("}}");
+ }
+ }
+
+ private void expressionToString(Expression expression) {
+ if (expression == null) {
+ return;
+ }
+ if (expression instanceof LiteralExpression) {
+ literalExpressionToString((LiteralExpression) expression);
+ } else if (expression instanceof VariableExpression) {
+ variableExpressionToString((VariableExpression) expression);
+ } else if (expression instanceof FunctionExpression) {
+ functionExpressionToString((FunctionExpression) expression);
+ } else if (expression instanceof Markup) {
+ markupToString((Markup) expression);
+ } else if (expression instanceof UnsupportedExpression) {
+ unsupportedExpressionToString((UnsupportedExpression) expression);
+ } else {
+ errorType("Expression", expression);
+ }
+ }
+
+ private void unsupportedExpressionToString(UnsupportedExpression ue) {
+ result.append('{');
+ annotationToString(ue.annotation);
+ attributesToString(ue.attributes);
+ result.append('}');
+ }
+
+ private void markupToString(Markup markup) {
+ result.append('{');
+ if (markup.kind == Markup.Kind.CLOSE) {
+ result.append('/');
+ } else {
+ result.append('#');
+ }
+ result.append(markup.name);
+ optionsToString(markup.options);
+ attributesToString(markup.attributes);
+ if (markup.kind == Markup.Kind.STANDALONE) {
+ result.append('/');
+ }
+ result.append('}');
+ }
+
+ private void optionsToString(Map<String, Option> options) {
+ for (Option option : options.values()) {
+ result.append(' ');
+ result.append(option.name);
+ result.append('=');
+ literalOrVariableRefToString(option.value);
+ }
+ }
+
+ private void functionExpressionToString(FunctionExpression fe) {
+ result.append('{');
+ annotationToString(fe.annotation);
+ attributesToString(fe.attributes);
+ result.append('}');
+ }
+
+ private void attributesToString(List<Attribute> attributes) {
+ if (attributes == null) {
+ return;
+ }
+ for (Attribute attribute : attributes) {
+ result.append(" @");
+ result.append(attribute.name);
+ // Attributes can be with without a value (for now?)
+ if (attribute.value != null) {
+ result.append('=');
+ literalOrVariableRefToString(attribute.value);
+ }
+ }
+ }
+
+ private void annotationToString(Annotation annotation) {
+ if (annotation == null) {
+ return;
+ }
+ if (annotation instanceof FunctionAnnotation) {
+ addSpaceIfNeeded();
+ result.append(":");
+ result.append(((FunctionAnnotation) annotation).name);
+ optionsToString(((FunctionAnnotation) annotation).options);
+ } else if (annotation instanceof UnsupportedAnnotation) {
+ addSpaceIfNeeded();
+ String value = ((UnsupportedAnnotation) annotation).source;
+ for (int i = 0; i < value.length(); i++) {
+ char c = value.charAt(i);
+ if (c == '\\' || c == '{' || c == '}') {
+ result.append('\\');
+ }
+ result.append(c);
+ }
+ } else {
+ errorType("Annotation", annotation);
+ }
+ }
+
+ private void variableExpressionToString(VariableExpression ve) {
+ if (ve == null) {
+ return;
+ }
+ result.append('{');
+ literalOrVariableRefToString(ve.arg);
+ needSpace = true;
+ annotationToString(ve.annotation);
+ attributesToString(ve.attributes);
+ result.append('}');
+ needSpace = false;
+ }
+
+ private void literalOrVariableRefToString(LiteralOrVariableRef literalOrVarRef) {
+ if (literalOrVarRef instanceof Literal) {
+ literalToString((Literal) literalOrVarRef);
+ } else if (literalOrVarRef instanceof VariableRef) {
+ result.append("$" + ((VariableRef) literalOrVarRef).name);
+ } else {
+ errorType("LiteralOrVariableRef", literalOrVarRef);
+ }
+ }
+
+ // abnf: number-literal = ["-"] (%x30 / (%x31-39 *DIGIT)) ["." 1*DIGIT]
+ // [%i"e" ["-" / "+"] 1*DIGIT]
+ // Not identical to the one in the parser. This one has a $ at the end, to
+ // match the whole string
+ // TBD if it can be refactored to reuse.
+ private static final java.util.regex.Pattern RE_NUMBER_LITERAL =
+ java.util.regex.Pattern.compile("^-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+\\-]?[0-9]+)?$");
+
+ private void literalToString(Literal literal) {
+ String value = literal.value;
+ Matcher matcher = RE_NUMBER_LITERAL.matcher(value);
+ if (matcher.find()) { // It is a number, output as is
+ result.append(value);
+ } else {
+ StringBuilder literalBuffer = new StringBuilder();
+ boolean wasName = true;
+ for (int i = 0; i < value.length(); i++) {
+ char c = value.charAt(i);
+ if (c == '\\' || c == '|') {
+ literalBuffer.append('\\');
+ }
+ literalBuffer.append(c);
+ if (i == 0 && !StringUtils.isNameStart(c)) {
+ wasName = false;
+ } else if (!StringUtils.isNameChar(c)) {
+ wasName = false;
+ }
+ }
+ if (wasName && literalBuffer.length() != 0) {
+ result.append(literalBuffer);
+ } else {
+ result.append('|');
+ result.append(literalBuffer);
+ result.append('|');
+ }
+ }
+ }
+
+ private void literalExpressionToString(LiteralExpression le) {
+ result.append('{');
+ literalOrVariableRefToString(le.arg);
+ needSpace = true;
+ annotationToString(le.annotation);
+ attributesToString(le.attributes);
+ result.append('}');
+ }
+
+ private void stringPartToString(StringPart part) {
+ if (part.value.startsWith(".")) {
+ if (!shouldDoubleQuotePattern) {
+ shouldDoubleQuotePattern = true;
+ result.append("{{");
+ }
+ }
+ for (int i = 0; i < part.value.length(); i++) {
+ char c = part.value.charAt(i);
+ if (c == '\\' || c == '{' || c == '}') {
+ result.append('\\');
+ }
+ result.append(c);
+ }
+ }
+
+ private void declarationsToString(List<Declaration> declarations) {
+ if (declarations == null || declarations.isEmpty()) {
+ return;
+ }
+ shouldDoubleQuotePattern = true;
+ for (Declaration declaration : declarations) {
+ if (declaration instanceof LocalDeclaration) {
+ localDeclarationToString((LocalDeclaration) declaration);
+ } else if (declaration instanceof InputDeclaration) {
+ inputDeclarationToString((InputDeclaration) declaration);
+ } else if (declaration instanceof UnsupportedStatement) {
+ unsupportedStatementToString((UnsupportedStatement) declaration);
+ } else {
+ errorTy