diff options
Diffstat (limited to 'android_icu4j/src/main/java/android/icu/impl')
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 */ |