/*
* Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/*
* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
* (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
*
* The original version of this source code and documentation
* is copyrighted and owned by Taligent, Inc., a wholly-owned
* subsidiary of IBM. These materials are provided under terms
* of a License Agreement between Taligent and Sun. This technology
* is protected by multiple US and International patents.
*
* This notice and attribution to Taligent may not be removed.
* Taligent is a registered trademark of Taligent, Inc.
*
*/
package java.util;
import java.io.*;
import java.security.AccessController;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.spi.LocaleNameProvider;
import java.util.spi.LocaleServiceProvider;
import sun.security.action.GetPropertyAction;
import sun.util.LocaleServiceProviderPool;
import sun.util.resources.LocaleData;
import sun.util.resources.OpenListResourceBundle;
/**
*
* A Locale
object represents a specific geographical, political,
* or cultural region. An operation that requires a Locale
to perform
* its task is called locale-sensitive and uses the Locale
* to tailor information for the user. For example, displaying a number
* is a locale-sensitive operation--the number should be formatted
* according to the customs/conventions of the user's native country,
* region, or culture.
*
*
* Create a Locale
object using the constructors in this class:
*
** The language argument is a valid ISO Language Code. * These codes are the lower-case, two-letter codes as defined by ISO-639. * You can find a full list of these codes at a number of sites, such as: ** Locale(String language) * Locale(String language, String country) * Locale(String language, String country, String variant) **
http://www.loc.gov/standards/iso639-2/php/English_list.php
*
*
* The country argument is a valid ISO Country Code. These
* codes are the upper-case, two-letter codes as defined by ISO-3166.
* You can find a full list of these codes at a number of sites, such as:
*
* http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html
*
*
* The variant argument is a vendor or browser-specific code. * For example, use WIN for Windows, MAC for Macintosh, and POSIX for POSIX. * Where there are two variants, separate them with an underscore, and * put the most important one first. For example, a Traditional Spanish collation * might construct a locale with parameters for language, country and variant as: * "es", "ES", "Traditional_WIN". * *
* Because a Locale
object is just an identifier for a region,
* no validity check is performed when you construct a Locale
.
* If you want to see whether particular resources are available for the
* Locale
you construct, you must query those resources. For
* example, ask the NumberFormat
for the locales it supports
* using its getAvailableLocales
method.
*
Note: When you ask for a resource for a particular
* locale, you get back the best available match, not necessarily
* precisely what you asked for. For more information, look at
* {@link ResourceBundle}.
*
*
* The Locale
class provides a number of convenient constants
* that you can use to create Locale
objects for commonly used
* locales. For example, the following creates a Locale
object
* for the United States:
*
** ** Locale.US **
* Once you've created a Locale
you can query it for information about
* itself. Use getCountry
to get the ISO Country Code and
* getLanguage
to get the ISO Language Code. You can
* use getDisplayCountry
to get the
* name of the country suitable for displaying to the user. Similarly,
* you can use getDisplayLanguage
to get the name of
* the language suitable for displaying to the user. Interestingly,
* the getDisplayXXX
methods are themselves locale-sensitive
* and have two versions: one that uses the default locale and one
* that uses the locale specified as an argument.
*
*
* The Java Platform provides a number of classes that perform locale-sensitive
* operations. For example, the NumberFormat
class formats
* numbers, currency, or percentages in a locale-sensitive manner. Classes
* such as NumberFormat
have a number of convenience methods
* for creating a default object of that type. For example, the
* NumberFormat
class provides these three convenience methods
* for creating a default NumberFormat
object:
*
** These methods have two variants; one with an explicit locale * and one without; the latter using the default locale. ** NumberFormat.getInstance() * NumberFormat.getCurrencyInstance() * NumberFormat.getPercentInstance() **
** A* NumberFormat.getInstance(myLocale) * NumberFormat.getCurrencyInstance(myLocale) * NumberFormat.getPercentInstance(myLocale) **
Locale
is the mechanism for identifying the kind of object
* (NumberFormat
) that you would like to get. The locale is
* just a mechanism for identifying objects,
* not a container for the objects themselves.
*
* @see ResourceBundle
* @see java.text.Format
* @see java.text.NumberFormat
* @see java.text.Collator
* @author Mark Davis
* @since 1.1
*/
public final class Locale implements Cloneable, Serializable {
// cache to store singleton Locales
private final static ConcurrentHashMapLocale
using language
* and country
. This constructor assumes that
* language
and contry
are interned and
* it is invoked by createSingleton only. (flag is just for
* avoiding the conflict with the public constructors.
*/
private Locale(String language, String country, boolean flag) {
this.language = language;
this.country = country;
this.variant = "";
}
/**
* Creates a Locale
instance with the given
* language
and counry
and puts the
* instance under the given key
in the cache. This
* method must be called only when initializing the Locale
* constants.
*/
private static Locale createSingleton(String key, String language, String country) {
Locale locale = new Locale(language, country, false);
cache.put(key, locale);
return locale;
}
/**
* Returns a Locale
constructed from the given
* language
, country
and
* variant
. If the same Locale
instance
* is available in the cache, then that instance is
* returned. Otherwise, a new Locale
instance is
* created and cached.
*
* @param language lowercase two-letter ISO-639 code.
* @param country uppercase two-letter ISO-3166 code.
* @param variant vendor and browser specific code. See class description.
* @return the Locale
instance requested
* @exception NullPointerException if any argument is null.
*/
static Locale getInstance(String language, String country, String variant) {
if (language== null || country == null || variant == null) {
throw new NullPointerException();
}
StringBuilder sb = new StringBuilder();
sb.append(language).append('_').append(country).append('_').append(variant);
String key = sb.toString();
Locale locale = cache.get(key);
if (locale == null) {
locale = new Locale(language, country, variant);
Locale l = cache.putIfAbsent(key, locale);
if (l != null) {
locale = l;
}
}
return locale;
}
/**
* Gets the current value of the default locale for this instance
* of the Java Virtual Machine.
* * The Java Virtual Machine sets the default locale during startup * based on the host environment. It is used by many locale-sensitive * methods if no locale is explicitly specified. * It can be changed using the * {@link #setDefault(java.util.Locale) setDefault} method. * * @return the default locale for this instance of the Java Virtual Machine */ public static Locale getDefault() { // do not synchronize this method - see 4071298 // it's OK if more than one default locale happens to be created if (defaultLocale == null) { String language, region, country, variant; language = AccessController.doPrivileged( new GetPropertyAction("user.language", "en")); // for compatibility, check for old user.region property region = AccessController.doPrivileged( new GetPropertyAction("user.region")); if (region != null) { // region can be of form country, country_variant, or _variant int i = region.indexOf('_'); if (i >= 0) { country = region.substring(0, i); variant = region.substring(i + 1); } else { country = region; variant = ""; } } else { country = AccessController.doPrivileged( new GetPropertyAction("user.country", "")); variant = AccessController.doPrivileged( new GetPropertyAction("user.variant", "")); } defaultLocale = getInstance(language, country, variant); } return defaultLocale; } /** * Sets the default locale for this instance of the Java Virtual Machine. * This does not affect the host locale. *
* If there is a security manager, its checkPermission
* method is called with a PropertyPermission("user.language", "write")
* permission before the default locale is changed.
*
* The Java Virtual Machine sets the default locale during startup * based on the host environment. It is used by many locale-sensitive * methods if no locale is explicitly specified. *
* Since changing the default locale may affect many different areas
* of functionality, this method should only be used if the caller
* is prepared to reinitialize locale-sensitive code running
* within the same Java Virtual Machine.
*
* @throws SecurityException
* if a security manager exists and its
* checkPermission
method doesn't allow the operation.
* @throws NullPointerException if newLocale
is null
* @param newLocale the new default locale
* @see SecurityManager#checkPermission
* @see java.util.PropertyPermission
*/
public static synchronized void setDefault(Locale newLocale) {
if (newLocale == null)
throw new NullPointerException("Can't set default locale to NULL");
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(new PropertyPermission
("user.language", "write"));
defaultLocale = newLocale;
}
/**
* Returns an array of all installed locales.
* The returned array represents the union of locales supported
* by the Java runtime environment and by installed
* {@link java.util.spi.LocaleServiceProvider LocaleServiceProvider}
* implementations. It must contain at least a Locale
* instance equal to {@link java.util.Locale#US Locale.US}.
*
* @return An array of installed locales.
*/
public static Locale[] getAvailableLocales() {
return LocaleServiceProviderPool.getAllAvailableLocales();
}
/**
* Returns a list of all 2-letter country codes defined in ISO 3166.
* Can be used to create Locales.
*/
public static String[] getISOCountries() {
if (isoCountries == null) {
isoCountries = getISO2Table(LocaleISOData.isoCountryTable);
}
String[] result = new String[isoCountries.length];
System.arraycopy(isoCountries, 0, result, 0, isoCountries.length);
return result;
}
/**
* Returns a list of all 2-letter language codes defined in ISO 639.
* Can be used to create Locales.
* [NOTE: ISO 639 is not a stable standard-- some languages' codes have changed.
* The list this function returns includes both the new and the old codes for the
* languages whose codes have changed.]
*/
public static String[] getISOLanguages() {
if (isoLanguages == null) {
isoLanguages = getISO2Table(LocaleISOData.isoLanguageTable);
}
String[] result = new String[isoLanguages.length];
System.arraycopy(isoLanguages, 0, result, 0, isoLanguages.length);
return result;
}
private static final String[] getISO2Table(String table) {
int len = table.length() / 5;
String[] isoTable = new String[len];
for (int i = 0, j = 0; i < len; i++, j += 5) {
isoTable[i] = table.substring(j, j + 2);
}
return isoTable;
}
/**
* Returns the language code for this locale, which will either be the empty string
* or a lowercase ISO 639 code.
*
NOTE: ISO 639 is not a stable standard-- some languages' codes have changed. * Locale's constructor recognizes both the new and the old codes for the languages * whose codes have changed, but this function always returns the old code. If you * want to check for a specific language whose code has changed, don't do
* if (locale.getLanguage().equals("he")) * ... *Instead, do
* if (locale.getLanguage().equals(new Locale("he", "", "").getLanguage())) * ...* @see #getDisplayLanguage */ public String getLanguage() { return language; } /** * Returns the country/region code for this locale, which will * either be the empty string or an uppercase ISO 3166 2-letter code. * @see #getDisplayCountry */ public String getCountry() { return country; } /** * Returns the variant code for this locale. * @see #getDisplayVariant */ public String getVariant() { return variant; } /** * Getter for the programmatic name of the entire locale, * with the language, country and variant separated by underbars. * Language is always lower case, and country is always upper case. * If the language is missing, the string will begin with an underbar. * If both the language and country fields are missing, this function * will return the empty string, even if the variant field is filled in * (you can't have a locale with just a variant-- the variant must accompany * a valid language or country code). * Examples: "en", "de_DE", "_GB", "en_US_WIN", "de__POSIX", "fr__MAC" * @see #getDisplayName */ public final String toString() { boolean l = language.length() != 0; boolean c = country.length() != 0; boolean v = variant.length() != 0; StringBuilder result = new StringBuilder(language); if (c||(l&&v)) { result.append('_').append(country); // This may just append '_' } if (v&&(l||c)) { result.append('_').append(variant); } return result.toString(); } /** * Returns a three-letter abbreviation for this locale's language. If the locale * doesn't specify a language, this will be the empty string. Otherwise, this will * be a lowercase ISO 639-2/T language code. * The ISO 639-2 language codes can be found on-line at * *
http://www.loc.gov/standards/iso639-2/englangn.html
.
* @exception MissingResourceException Throws MissingResourceException if the
* three-letter language abbreviation is not available for this locale.
*/
public String getISO3Language() throws MissingResourceException {
String language3 = getISO3Code(language, LocaleISOData.isoLanguageTable);
if (language3 == null) {
throw new MissingResourceException("Couldn't find 3-letter language code for "
+ language, "FormatData_" + toString(), "ShortLanguage");
}
return language3;
}
/**
* Returns a three-letter abbreviation for this locale's country. If the locale
* doesn't specify a country, this will be the empty string. Otherwise, this will
* be an uppercase ISO 3166 3-letter country code.
* The ISO 3166-2 country codes can be found on-line at
*
* http://www.davros.org/misc/iso3166.txt
.
* @exception MissingResourceException Throws MissingResourceException if the
* three-letter country abbreviation is not available for this locale.
*/
public String getISO3Country() throws MissingResourceException {
String country3 = getISO3Code(country, LocaleISOData.isoCountryTable);
if (country3 == null) {
throw new MissingResourceException("Couldn't find 3-letter country code for "
+ country, "FormatData_" + toString(), "ShortCountry");
}
return country3;
}
private static final String getISO3Code(String iso2Code, String table) {
int codeLength = iso2Code.length();
if (codeLength == 0) {
return "";
}
int tableLength = table.length();
int index = tableLength;
if (codeLength == 2) {
char c1 = iso2Code.charAt(0);
char c2 = iso2Code.charAt(1);
for (index = 0; index < tableLength; index += 5) {
if (table.charAt(index) == c1
&& table.charAt(index + 1) == c2) {
break;
}
}
}
return index < tableLength ? table.substring(index + 2, index + 5) : null;
}
/**
* Returns a name for the locale's language that is appropriate for display to the
* user.
* If possible, the name returned will be localized for the default locale.
* For example, if the locale is fr_FR and the default locale
* is en_US, getDisplayLanguage() will return "French"; if the locale is en_US and
* the default locale is fr_FR, getDisplayLanguage() will return "anglais".
* If the name returned cannot be localized for the default locale,
* (say, we don't have a Japanese name for Croatian),
* this function falls back on the English name, and uses the ISO code as a last-resort
* value. If the locale doesn't specify a language, this function returns the empty string.
*/
public final String getDisplayLanguage() {
return getDisplayLanguage(getDefault());
}
/**
* Returns a name for the locale's language that is appropriate for display to the
* user.
* If possible, the name returned will be localized according to inLocale.
* For example, if the locale is fr_FR and inLocale
* is en_US, getDisplayLanguage() will return "French"; if the locale is en_US and
* inLocale is fr_FR, getDisplayLanguage() will return "anglais".
* If the name returned cannot be localized according to inLocale,
* (say, we don't have a Japanese name for Croatian),
* this function falls back on the English name, and finally
* on the ISO code as a last-resort value. If the locale doesn't specify a language,
* this function returns the empty string.
*
* @exception NullPointerException if inLocale
is null
*/
public String getDisplayLanguage(Locale inLocale) {
return getDisplayString(language, inLocale, DISPLAY_LANGUAGE);
}
/**
* Returns a name for the locale's country that is appropriate for display to the
* user.
* If possible, the name returned will be localized for the default locale.
* For example, if the locale is fr_FR and the default locale
* is en_US, getDisplayCountry() will return "France"; if the locale is en_US and
* the default locale is fr_FR, getDisplayCountry() will return "Etats-Unis".
* If the name returned cannot be localized for the default locale,
* (say, we don't have a Japanese name for Croatia),
* this function falls back on the English name, and uses the ISO code as a last-resort
* value. If the locale doesn't specify a country, this function returns the empty string.
*/
public final String getDisplayCountry() {
return getDisplayCountry(getDefault());
}
/**
* Returns a name for the locale's country that is appropriate for display to the
* user.
* If possible, the name returned will be localized according to inLocale.
* For example, if the locale is fr_FR and inLocale
* is en_US, getDisplayCountry() will return "France"; if the locale is en_US and
* inLocale is fr_FR, getDisplayCountry() will return "Etats-Unis".
* If the name returned cannot be localized according to inLocale.
* (say, we don't have a Japanese name for Croatia),
* this function falls back on the English name, and finally
* on the ISO code as a last-resort value. If the locale doesn't specify a country,
* this function returns the empty string.
*
* @exception NullPointerException if inLocale
is null
*/
public String getDisplayCountry(Locale inLocale) {
return getDisplayString(country, inLocale, DISPLAY_COUNTRY);
}
private String getDisplayString(String code, Locale inLocale, int type) {
if (code.length() == 0) {
return "";
}
if (inLocale == null) {
throw new NullPointerException();
}
try {
OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale);
String key = (type == DISPLAY_VARIANT ? "%%"+code : code);
String result = null;
// Check whether a provider can provide an implementation that's closer
// to the requested locale than what the Java runtime itself can provide.
LocaleServiceProviderPool pool =
LocaleServiceProviderPool.getPool(LocaleNameProvider.class);
if (pool.hasProviders()) {
result = pool.getLocalizedObject(
LocaleNameGetter.INSTANCE,
inLocale, bundle, key,
type, code);
}
if (result == null) {
result = bundle.getString(key);
}
if (result != null) {
return result;
}
}
catch (Exception e) {
// just fall through
}
return code;
}
/**
* Returns a name for the locale's variant code that is appropriate for display to the
* user. If possible, the name will be localized for the default locale. If the locale
* doesn't specify a variant code, this function returns the empty string.
*/
public final String getDisplayVariant() {
return getDisplayVariant(getDefault());
}
/**
* Returns a name for the locale's variant code that is appropriate for display to the
* user. If possible, the name will be localized for inLocale. If the locale
* doesn't specify a variant code, this function returns the empty string.
*
* @exception NullPointerException if inLocale
is null
*/
public String getDisplayVariant(Locale inLocale) {
if (variant.length() == 0)
return "";
OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale);
String names[] = getDisplayVariantArray(bundle, inLocale);
// Get the localized patterns for formatting a list, and use
// them to format the list.
String listPattern = null;
String listCompositionPattern = null;
try {
listPattern = bundle.getString("ListPattern");
listCompositionPattern = bundle.getString("ListCompositionPattern");
} catch (MissingResourceException e) {
}
return formatList(names, listPattern, listCompositionPattern);
}
/**
* Returns a name for the locale that is appropriate for display to the
* user. This will be the values returned by getDisplayLanguage(), getDisplayCountry(),
* and getDisplayVariant() assembled into a single string. The display name will have
* one of the following forms:* language (country, variant)* depending on which fields are specified in the locale. If the language, country, * and variant fields are all empty, this function returns the empty string. */ public final String getDisplayName() { return getDisplayName(getDefault()); } /** * Returns a name for the locale that is appropriate for display to the * user. This will be the values returned by getDisplayLanguage(), getDisplayCountry(), * and getDisplayVariant() assembled into a single string. The display name will have * one of the following forms:* language (country)
* language (variant)
* country (variant)
* language
* country
* variant
* language (country, variant)* depending on which fields are specified in the locale. If the language, country, * and variant fields are all empty, this function returns the empty string. * * @exception NullPointerException if* language (country)
* language (variant)
* country (variant)
* language
* country
* variant
inLocale
is null
*/
public String getDisplayName(Locale inLocale) {
OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale);
String languageName = getDisplayLanguage(inLocale);
String countryName = getDisplayCountry(inLocale);
String[] variantNames = getDisplayVariantArray(bundle, inLocale);
// Get the localized patterns for formatting a display name.
String displayNamePattern = null;
String listPattern = null;
String listCompositionPattern = null;
try {
displayNamePattern = bundle.getString("DisplayNamePattern");
listPattern = bundle.getString("ListPattern");
listCompositionPattern = bundle.getString("ListCompositionPattern");
} catch (MissingResourceException e) {
}
// The display name consists of a main name, followed by qualifiers.
// Typically, the format is "MainName (Qualifier, Qualifier)" but this
// depends on what pattern is stored in the display locale.
String mainName = null;
String[] qualifierNames = null;
// The main name is the language, or if there is no language, the country.
// If there is neither language nor country (an anomalous situation) then
// the display name is simply the variant's display name.
if (languageName.length() != 0) {
mainName = languageName;
if (countryName.length() != 0) {
qualifierNames = new String[variantNames.length + 1];
System.arraycopy(variantNames, 0, qualifierNames, 1, variantNames.length);
qualifierNames[0] = countryName;
}
else qualifierNames = variantNames;
}
else if (countryName.length() != 0) {
mainName = countryName;
qualifierNames = variantNames;
}
else {
return formatList(variantNames, listPattern, listCompositionPattern);
}
// Create an array whose first element is the number of remaining
// elements. This serves as a selector into a ChoiceFormat pattern from
// the resource. The second and third elements are the main name and
// the qualifier; if there are no qualifiers, the third element is
// unused by the format pattern.
Object[] displayNames = {
new Integer(qualifierNames.length != 0 ? 2 : 1),
mainName,
// We could also just call formatList() and have it handle the empty
// list case, but this is more efficient, and we want it to be
// efficient since all the language-only locales will not have any
// qualifiers.
qualifierNames.length != 0 ? formatList(qualifierNames, listPattern, listCompositionPattern) : null
};
if (displayNamePattern != null) {
return new MessageFormat(displayNamePattern).format(displayNames);
}
else {
// If we cannot get the message format pattern, then we use a simple
// hard-coded pattern. This should not occur in practice unless the
// installation is missing some core files (FormatData etc.).
StringBuilder result = new StringBuilder();
result.append((String)displayNames[1]);
if (displayNames.length > 2) {
result.append(" (");
result.append((String)displayNames[2]);
result.append(')');
}
return result.toString();
}
}
/**
* Overrides Cloneable
*/
public Object clone()
{
try {
Locale that = (Locale)super.clone();
return that;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
/**
* Override hashCode.
* Since Locales are often used in hashtables, caches the value
* for speed.
*/
public int hashCode() {
int hc = hashCodeValue;
if (hc == 0) {
hc = (language.hashCode() << 8) ^ country.hashCode() ^ (variant.hashCode() << 4);
hashCodeValue = hc;
}
return hc;
}
// Overrides
/**
* Returns true if this Locale is equal to another object. A Locale is
* deemed equal to another Locale with identical language, country,
* and variant, and unequal to all other objects.
*
* @return true if this Locale is equal to the specified object.
*/
public boolean equals(Object obj) {
if (this == obj) // quick check
return true;
if (!(obj instanceof Locale))
return false;
Locale other = (Locale) obj;
return language == other.language
&& country == other.country
&& variant == other.variant;
}
// ================= privates =====================================
// XXX instance and class variables. For now keep these separate, since it is
// faster to match. Later, make into single string.
/**
* @serial
* @see #getLanguage
*/
private final String language;
/**
* @serial
* @see #getCountry
*/
private final String country;
/**
* @serial
* @see #getVariant
*/
private final String variant;
/**
* Placeholder for the object's hash code. Always -1.
* @serial
*/
private volatile int hashcode = -1; // lazy evaluate
/**
* Calculated hashcode to fix 4518797.
*/
private transient volatile int hashCodeValue = 0;
private static Locale defaultLocale = null;
/**
* Return an array of the display names of the variant.
* @param bundle the ResourceBundle to use to get the display names
* @return an array of display names, possible of zero length.
*/
private String[] getDisplayVariantArray(OpenListResourceBundle bundle, Locale inLocale) {
// Split the variant name into tokens separated by '_'.
StringTokenizer tokenizer = new StringTokenizer(variant, "_");
String[] names = new String[tokenizer.countTokens()];
// For each variant token, lookup the display name. If
// not found, use the variant name itself.
for (int i=0; i