summaryrefslogtreecommitdiff
path: root/android_icu4j/src/main/java/android/icu/impl
diff options
context:
space:
mode:
Diffstat (limited to 'android_icu4j/src/main/java/android/icu/impl')
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/CalendarAstronomer.java712
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/ICUService.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java8
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/LocaleIDParser.java13
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/PropsVectors.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java142
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/ZoneMeta.java120
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/breakiter/BurmeseBreakEngine.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/breakiter/KhmerBreakEngine.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/breakiter/LaoBreakEngine.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/LSR.java111
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/LikelySubtags.java (renamed from android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java)72
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/LocaleDistance.java8
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/locale/LocaleExtensions.java20
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/ConstantAffixModifier.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/ConstantMultiFieldModifier.java5
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/DecimalQuantity_AbstractBCD.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/Grouper.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/Modifier.java40
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/MutablePatternModifier.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/number/SimpleModifier.java5
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java37
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/units/MeasureUnitImpl.java14
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java2
-rw-r--r--android_icu4j/src/main/java/android/icu/impl/units/UnitsConverter.java157
28 files changed, 580 insertions, 910 deletions
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 088100dbe..e616c9cad 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 @@ public class CalendarAstronomer {
/**
* 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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
/**
* 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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
};
}
- /**
- * 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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
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 class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
* 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 @@ public class CalendarAstronomer {
*/
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 @@ public class CalendarAstronomer {
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 @@ public class CalendarAstronomer {
* 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 @@ public class CalendarAstronomer {
* 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 class CalendarAstronomer {
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/DateNumberFormat.java b/android_icu4j/src/main/java/android/icu/impl/DateNumberFormat.java
index f160e7872..4d3068798 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 @@ public final class DateNumberFormat extends NumberFormat {
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/ICUResourceBundle.java b/android_icu4j/src/main/java/android/icu/impl/ICUResourceBundle.java
index f4ae5ae9a..3ed55c054 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 @@ public class ICUResourceBundle extends UResourceBundle {
}
}
} 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
}
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 d6ebaa4b2..e34ad4c02 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 @@ public class ICUService extends ICUNotifier {
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 d812a2803..4e61b68b2 100644
--- a/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java
+++ b/android_icu4j/src/main/java/android/icu/impl/LocaleFallbackData.java
@@ -51,7 +51,6 @@ class LocaleFallbackData {
t.put("ain", "Kana");
t.put("aio", "Mymr");
t.put("aiq", "Arab");
- t.put("ajp", "Arab");
t.put("akk", "Xsux");
t.put("akv", "Cyrl");
t.put("alk", "Laoo");
@@ -389,7 +388,6 @@ class LocaleFallbackData {
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");
@@ -488,6 +486,7 @@ class LocaleFallbackData {
t.put("ktb", "Ethi");
t.put("ktl", "Arab");
t.put("ktp", "Plrd");
+ t.put("ku_IR", "Arab");
t.put("ku_LB", "Arab");
t.put("kuf", "Laoo");
t.put("kum", "Cyrl");
@@ -710,7 +709,6 @@ class LocaleFallbackData {
t.put("pra", "Khar");
t.put("prc", "Arab");
t.put("prd", "Arab");
- t.put("prp", "Gujr");
t.put("prt", "Thai");
t.put("prx", "Arab");
t.put("ps", "Arab");
@@ -796,7 +794,6 @@ class LocaleFallbackData {
t.put("skb", "Thai");
t.put("skj", "Deva");
t.put("skr", "Arab");
- t.put("slq", "Arab");
t.put("smh", "Yiii");
t.put("smp", "Samr");
t.put("smu", "Khmr");
@@ -867,7 +864,6 @@ class LocaleFallbackData {
t.put("tkb", "Deva");
t.put("tks", "Arab");
t.put("tkt", "Deva");
- t.put("tmk", "Deva");
t.put("tmr", "Syrc");
t.put("tnv", "Cakm");
t.put("tov", "Arab");
@@ -962,7 +958,6 @@ class LocaleFallbackData {
t.put("xrn", "Cyrl");
t.put("xsa", "Sarb");
t.put("xsr", "Deva");
- t.put("xss", "Cyrl");
t.put("xub", "Taml");
t.put("xuj", "Taml");
t.put("xve", "Ital");
@@ -1028,7 +1023,6 @@ class LocaleFallbackData {
t.put("zh_VN", "Hant");
t.put("zhd", "Hani");
t.put("zhx", "Nshu");
- t.put("zkb", "Cyrl");
t.put("zko", "Cyrl");
t.put("zkt", "Kits");
t.put("zkz", "Cyrl");
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 a9f3186e7..10e066531 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 @@ public final class LocaleIDParser {
}
}
+ // 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 @@ public final class LocaleIDParser {
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/PropsVectors.java b/android_icu4j/src/main/java/android/icu/impl/PropsVectors.java
index 99f52fd27..490d48a9b 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 @@ public class PropsVectors {
// 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/UCharacterProperty.java b/android_icu4j/src/main/java/android/icu/impl/UCharacterProperty.java
index c451bc858..3aba5b2d7 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 @@ package android.icu.impl;
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;
@@ -865,6 +868,18 @@ public final class UCharacterProperty
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) {
@@ -938,6 +953,7 @@ public final class UCharacterProperty
} else {
switch(which) {
case UProperty.SCRIPT_EXTENSIONS:
+ case UProperty.IDENTIFIER_TYPE:
return SRC_PROPSVEC;
default:
return SRC_NONE; /* undefined */
@@ -1445,20 +1461,73 @@ public final class UCharacterProperty
/*
* 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;
@@ -1565,7 +1634,7 @@ public final class UCharacterProperty
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"
@@ -1736,6 +1805,61 @@ public final class UCharacterProperty
}
}
+ 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/ZoneMeta.java b/android_icu4j/src/main/java/android/icu/impl/ZoneMeta.java
index 0a874dae7..c96c991c3 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;
@@ -504,7 +502,7 @@ public final class ZoneMeta {
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);
}
@@ -649,8 +647,7 @@ public final class ZoneMeta {
// 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;
@@ -682,66 +679,25 @@ public final class ZoneMeta {
* @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 /*'+'*/) {
- 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) {
+ } else if (id.charAt(pos[0]) != 0x002B /*'+'*/) {
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
@@ -749,27 +705,57 @@ public final class ZoneMeta {
// 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 6b0bfbc24..4aecc01bc 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 @@ public class BurmeseBreakEngine extends DictionaryBreakEngine {
// 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 f746b9836..6bbaa9f98 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 @@ public class KhmerBreakEngine extends DictionaryBreakEngine {
// 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 9db413de5..22b4343b0 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 @@ public class LaoBreakEngine extends DictionaryBreakEngine {
// 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/ThaiBreakEngine.java b/android_icu4j/src/main/java/android/icu/impl/breakiter/ThaiBreakEngine.java
index 9d663bd12..4f32d462b 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 @@ public class ThaiBreakEngine extends DictionaryBreakEngine {
// 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/locale/LSR.java b/android_icu4j/src/main/java/android/icu/impl/locale/LSR.java
index bf62ae93b..c774af479 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,9 +3,12 @@
// 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;
/**
@@ -151,45 +154,91 @@ public final class LSR {
return (encodeLanguageToInt() + (27*27*27) * encodeRegionToInt(m49)) |
(encodeScriptToInt() << 24);
}
- private static String toLanguage(int encoded) {
- if (encoded == 0) return "";
- if (encoded == 1) return "skip";
- encoded &= 0x00ffffff;
- encoded %= 27*27*27;
- 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)));
+
+ // 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 res.toString();
- }
- private static String toScript(int encoded) {
- if (encoded == 0) return "";
- if (encoded == 1) return "script";
- encoded = (encoded >> 24) & 0x000000ff;
- return UScript.getShortName(encoded);
- }
- private static String toRegion(int encoded, String[] m49) {
- if (encoded == 0 || encoded == 1) return "";
- encoded &= 0x00ffffff;
- encoded /= 27 * 27 * 27;
- encoded %= 27 * 27;
- if (encoded < 27) {
- return m49[encoded];
+
+ /**
+ * @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();
}
- 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 n = nums[i];
- lsrs[i] = new LSR(toLanguage(n), toScript(n), toRegion(n, m49), LSR.IMPLICIT_LSR);
+ 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/XLikelySubtags.java b/android_icu4j/src/main/java/android/icu/impl/locale/LikelySubtags.java
index 5c3d2f63a..6c4b699c9 100644
--- a/android_icu4j/src/main/java/android/icu/impl/locale/XLikelySubtags.java
+++ b/android_icu4j/src/main/java/android/icu/impl/locale/LikelySubtags.java
@@ -22,7 +22,7 @@ import android.icu.util.ULocale;
/**
* @hide Only a subset of ICU is exposed in Android
*/
-public final class XLikelySubtags {
+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
@@ -116,7 +116,7 @@ public final class XLikelySubtags {
}
// VisibleForTesting
- public static final XLikelySubtags INSTANCE = new XLikelySubtags(Data.load());
+ public static final LikelySubtags INSTANCE = new LikelySubtags(Data.load());
private final Map<String, String> languageAliases;
private final Map<String, String> regionAliases;
@@ -131,7 +131,7 @@ public final class XLikelySubtags {
private final long[] trieFirstLetterStates = new long[26];
private final LSR[] lsrs;
- private XLikelySubtags(XLikelySubtags.Data data) {
+ private LikelySubtags(LikelySubtags.Data data) {
languageAliases = data.languageAliases;
regionAliases = data.regionAliases;
trie = new BytesTrie(data.trie, 0);
@@ -220,40 +220,42 @@ public final class XLikelySubtags {
// 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 (!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;
+ 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;
+ }
}
}
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 f04476ae5..441610807 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
@@ -230,8 +230,8 @@ public class LocaleDistance {
// VisibleForTesting
public int testOnlyDistance(ULocale desired, ULocale supported,
int threshold, FavorSubtag favorSubtag) {
- LSR supportedLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(supported, false);
- LSR desiredLSR = XLikelySubtags.INSTANCE.makeMaximizedLsrFrom(desired, false);
+ 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);
@@ -255,7 +255,7 @@ public class LocaleDistance {
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];
@@ -374,7 +374,7 @@ public class LocaleDistance {
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 0e1ed8971..94242c0c9 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 @@ public class LocaleExtensions {
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 @@ public class LocaleExtensions {
}
// 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 @@ public class LocaleExtensions {
}
Extension e = new Extension(key, AsciiUtil.toLowerString(value));
- _map.put(Character.valueOf(key), e);
+ _map.put(key, e);
}
}
@@ -111,7 +111,7 @@ public class LocaleExtensions {
}
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 class LocaleExtensions {
}
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 class LocaleExtensions {
}
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 class LocaleExtensions {
}
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 class LocaleExtensions {
}
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/number/ConstantAffixModifier.java b/android_icu4j/src/main/java/android/icu/impl/number/ConstantAffixModifier.java
index e3c970cf1..349b32650 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 @@ public class ConstantAffixModifier implements Modifier {
}
@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 19b21d3f2..a9b780b1b 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 @@ public class ConstantMultiFieldModifier implements Modifier {
}
@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 6e24f9655..b75ae8c96 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 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
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 932a8148c..b706bcb0a 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 @@ public class Grouper {
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/Modifier.java b/android_icu4j/src/main/java/android/icu/impl/number/Modifier.java
index db6c27d60..08a415690 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 @@ public interface Modifier {
POS;
static final int COUNT = Signum.values().length;
+ public static final Signum[] VALUES = Signum.values();
};
/**
@@ -87,8 +88,45 @@ public interface Modifier {
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 e84af1ee2..b4bce0019 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 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPr
}
@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 8a34133dc..34ad9f848 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 @@ public class SimpleModifier implements Modifier {
}
@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/units/ConversionRates.java b/android_icu4j/src/main/java/android/icu/impl/units/ConversionRates.java
index 3f11202bd..bc179c6c1 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 java.util.HashMap;
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;
@@ -81,6 +82,15 @@ public class ConversionRates {
}
+ // 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);
@@ -169,6 +179,7 @@ public class ConversionRates {
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);
@@ -182,18 +193,20 @@ public class ConversionRates {
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, systems 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, systems));
+ mapToConversionRate.put(simpleUnit, new ConversionRateInfo(simpleUnit, target, factor, offset, special, systems));
}
@@ -214,13 +227,15 @@ public class ConversionRates {
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, String systems) {
+ 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;
}
@@ -256,12 +271,24 @@ public class ConversionRates {
* @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; }
+ 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 1277b1b28..975665b1e 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 @@ public class MeasureUnitImpl {
@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/UnitPreferences.java b/android_icu4j/src/main/java/android/icu/impl/units/UnitPreferences.java
index e884b15af..2c8a42d7a 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
@@ -96,7 +96,7 @@ public class UnitPreferences {
}
}
- String region = ULocale.getRegionForSupplementalData(locale, false);
+ String region = ULocale.getRegionForSupplementalData(locale, true);
// Check the locale system tag, e.g `ms=metric`.
String localeSystem = locale.getKeywordValue("measure");
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 0eb1c2e24..d18e4e735 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 static java.math.MathContext.DECIMAL128;
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 @@ public class UnitsConverter {
private BigDecimal conversionRate;
private boolean reciprocal;
private BigDecimal offset;
+ private String specialSource;
+ private String specialTarget;
/**
* Constructor of <code>UnitsConverter</code>.
@@ -46,6 +50,7 @@ public class UnitsConverter {
* 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 @@ public class UnitsConverter {
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 @@ public class UnitsConverter {
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 @@ public class UnitsConverter {
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 @@ public class UnitsConverter {
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
*/