Merge "Migrate libcore code to new classes libcore.util.Mutable{Int,Long}."
diff --git a/libart/src/main/java/java/lang/AndroidHardcodedSystemProperties.java b/libart/src/main/java/java/lang/AndroidHardcodedSystemProperties.java
index 5a84c8e..87c3096 100644
--- a/libart/src/main/java/java/lang/AndroidHardcodedSystemProperties.java
+++ b/libart/src/main/java/java/lang/AndroidHardcodedSystemProperties.java
@@ -87,7 +87,7 @@
// Hardcode MessagePattern apostrophe mode to be default. b/27265238
{ "android.icu.text.MessagePattern.ApostropheMode", null },
- // Hardcode "sun.io.useCanonCaches" to use the default (on). b/28174137
+ // Hardcode "sun.io.useCanonCaches" to use the default (off). b/28174137, b/62301183
{ "sun.io.useCanonCaches", null },
{ "sun.io.useCanonPrefixCache", null },
@@ -111,4 +111,3 @@
{ "java.util.logging.manager", null },
};
}
-
diff --git a/luni/src/main/java/libcore/icu/LocaleData.java b/luni/src/main/java/libcore/icu/LocaleData.java
index 7d18098..154ea68 100644
--- a/luni/src/main/java/libcore/icu/LocaleData.java
+++ b/luni/src/main/java/libcore/icu/LocaleData.java
@@ -95,7 +95,7 @@
public char groupingSeparator;
public char patternSeparator;
public String percent;
- public char perMill;
+ public String perMill;
public char monetarySeparator;
public String minusSign;
public String exponentSeparator;
diff --git a/luni/src/main/java/libcore/icu/TimeZoneNames.java b/luni/src/main/java/libcore/icu/TimeZoneNames.java
index 68e6e30..aea4483 100644
--- a/luni/src/main/java/libcore/icu/TimeZoneNames.java
+++ b/luni/src/main/java/libcore/icu/TimeZoneNames.java
@@ -175,7 +175,5 @@
return ids.toArray(new String[ids.size()]);
}
- public static native String getExemplarLocation(String locale, String tz);
-
private static native void fillZoneStrings(String locale, String[][] result);
}
diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java
index c6053dc..f6d2456 100644
--- a/luni/src/main/java/libcore/io/BlockGuardOs.java
+++ b/luni/src/main/java/libcore/io/BlockGuardOs.java
@@ -52,14 +52,6 @@
}
}
- private void untagSocket(FileDescriptor fd) throws ErrnoException {
- try {
- SocketTagger.get().untag(fd);
- } catch (SocketException e) {
- throw new ErrnoException("socket", EINVAL, e);
- }
- }
-
@Override public FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException {
BlockGuard.getThreadPolicy().onNetwork();
final FileDescriptor acceptFd = os.accept(fd, peerAddress);
@@ -96,9 +88,6 @@
// connections in methods like onDestroy which will run on the UI thread.
BlockGuard.getThreadPolicy().onNetwork();
}
- if (isInetSocket(fd)) {
- untagSocket(fd);
- }
}
} catch (ErrnoException ignored) {
// We're called via Socket.close (which doesn't ask for us to be called), so we
diff --git a/luni/src/main/java/libcore/util/TimeZoneFinder.java b/luni/src/main/java/libcore/util/TimeZoneFinder.java
index 37e3b4a..fa15086 100644
--- a/luni/src/main/java/libcore/util/TimeZoneFinder.java
+++ b/luni/src/main/java/libcore/util/TimeZoneFinder.java
@@ -36,6 +36,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
/**
@@ -45,18 +46,19 @@
private static final String TZLOOKUP_FILE_NAME = "tzlookup.xml";
private static final String TIMEZONES_ELEMENT = "timezones";
+ private static final String IANA_VERSION_ATTRIBUTE = "ianaversion";
private static final String COUNTRY_ZONES_ELEMENT = "countryzones";
private static final String COUNTRY_ELEMENT = "country";
private static final String COUNTRY_CODE_ATTRIBUTE = "code";
+ private static final String DEFAULT_TIME_ZONE_ID_ATTRIBUTE = "default";
private static final String ID_ELEMENT = "id";
private static TimeZoneFinder instance;
private final ReaderSupplier xmlSource;
- // Cached fields for the last country looked up.
- private String lastCountryIso;
- private List<TimeZone> lastCountryTimeZones;
+ // Cached field for the last country looked up.
+ private CountryTimeZones lastCountryTimeZones;
private TimeZoneFinder(ReaderSupplier xmlSource) {
this.xmlSource = xmlSource;
@@ -131,6 +133,33 @@
}
/**
+ * Returns the IANA rules version associated with the data. If there is no version information
+ * or there is a problem reading the file then {@code null} is returned.
+ */
+ public String getIanaVersion() {
+ try (Reader reader = xmlSource.get()) {
+ XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
+ xmlPullParserFactory.setNamespaceAware(false);
+
+ XmlPullParser parser = xmlPullParserFactory.newPullParser();
+ parser.setInput(reader);
+
+ /*
+ * The expected XML structure is:
+ * <timezones ianaversion="xxxxx">
+ * ....
+ * </timezones>
+ */
+
+ findRequiredStartTag(parser, TIMEZONES_ELEMENT);
+
+ return parser.getAttributeValue(null /* namespace */, IANA_VERSION_ATTRIBUTE);
+ } catch (XmlPullParserException | IOException e) {
+ return null;
+ }
+ }
+
+ /**
* Returns a frozen ICU time zone that has / would have had the specified offset and DST value
* at the specified moment in the specified country.
*
@@ -143,14 +172,14 @@
public TimeZone lookupTimeZoneByCountryAndOffset(
String countryIso, int offsetSeconds, boolean isDst, long whenMillis, TimeZone bias) {
+ countryIso = normalizeCountryIso(countryIso);
List<TimeZone> candidates = lookupTimeZonesByCountry(countryIso);
if (candidates == null || candidates.isEmpty()) {
return null;
}
TimeZone firstMatch = null;
- for (int i = 0; i < candidates.size(); i++) {
- TimeZone match = candidates.get(i);
+ for (TimeZone match : candidates) {
if (!offsetMatchesAtTime(match, offsetSeconds, isDst, whenMillis)) {
continue;
}
@@ -193,45 +222,88 @@
}
/**
+ * Returns a "default" time zone ID known to be used in the specified country. This is
+ * the time zone ID that can be used if only the country code is known and can be presumed to be
+ * the "best" choice in the absence of other information. For countries with more than one zone
+ * the time zone will not be correct for everybody.
+ *
+ * <p>If the country code is not recognized or there is an error during lookup this can return
+ * null.
+ */
+ public String lookupDefaultTimeZoneIdByCountry(String countryIso) {
+ countryIso = normalizeCountryIso(countryIso);
+ CountryTimeZones countryTimeZones = findCountryTimeZones(countryIso);
+ return countryTimeZones == null ? null : countryTimeZones.getDefaultTimeZoneId();
+ }
+
+ /**
* Returns an immutable list of frozen ICU time zones known to be used in the specified country.
* If the country code is not recognized or there is an error during lookup this can return
* null. The TimeZones returned will never contain {@link TimeZone#UNKNOWN_ZONE}. This method
- * can return an empty list in a case when the underlying configuration references only unknown
+ * can return an empty list in a case when the underlying data files reference only unknown
* zone IDs.
*/
public List<TimeZone> lookupTimeZonesByCountry(String countryIso) {
- synchronized(this) {
- if (countryIso.equals(lastCountryIso)) {
+ countryIso = normalizeCountryIso(countryIso);
+ CountryTimeZones countryTimeZones = findCountryTimeZones(countryIso);
+ return countryTimeZones == null ? null : countryTimeZones.getTimeZones();
+ }
+
+ /**
+ * Returns an immutable list of time zone IDs known to be used in the specified country.
+ * If the country code is not recognized or there is an error during lookup this can return
+ * null. The IDs returned will all be valid for use with
+ * {@link java.util.TimeZone#getTimeZone(String)} and
+ * {@link android.icu.util.TimeZone#getTimeZone(String)}. This method can return an empty list
+ * in a case when the underlying data files reference only unknown zone IDs.
+ */
+ public List<String> lookupTimeZoneIdsByCountry(String countryIso) {
+ countryIso = normalizeCountryIso(countryIso);
+ CountryTimeZones countryTimeZones = findCountryTimeZones(countryIso);
+ return countryTimeZones == null ? null : countryTimeZones.getTimeZoneIds();
+ }
+
+ /**
+ * Returns a {@link CountryTimeZones} object associated with the specified country code.
+ * Caching is handled as needed. If the country code is not recognized or there is an error
+ * during lookup this can return null.
+ */
+ private CountryTimeZones findCountryTimeZones(String countryIso) {
+ synchronized (this) {
+ if (lastCountryTimeZones != null
+ && lastCountryTimeZones.getCountryIso().equals(countryIso)) {
return lastCountryTimeZones;
}
}
- CountryZonesExtractor extractor = new CountryZonesExtractor(countryIso);
- List<TimeZone> countryTimeZones = null;
+ SelectiveCountryTimeZonesExtractor extractor =
+ new SelectiveCountryTimeZonesExtractor(countryIso);
try {
processXml(extractor);
- countryTimeZones = extractor.getMatchedZones();
- } catch (IOException e) {
+
+ CountryTimeZones countryTimeZones = extractor.getValidatedCountryTimeZones();
+ if (countryTimeZones == null) {
+ // None matched. Return the null but don't change the cached value.
+ return null;
+ }
+
+ // Update the cached value.
+ synchronized (this) {
+ lastCountryTimeZones = countryTimeZones;
+ }
+ return countryTimeZones;
+ } catch (XmlPullParserException | IOException e) {
System.logW("Error reading country zones ", e);
- // Clear the cached code so we will try again next time.
- countryIso = null;
- } catch (XmlPullParserException e) {
- System.logW("Error reading country zones ", e);
- // We want to cache the null. This won't get better over time.
+ // Error - don't change the cached value.
+ return null;
}
-
- synchronized(this) {
- lastCountryIso = countryIso;
- lastCountryTimeZones = countryTimeZones;
- }
- return countryTimeZones;
}
/**
* Processes the XML, applying the {@link CountryZonesProcessor} to the <countryzones>
* element. Processing can terminate early if the
- * {@link CountryZonesProcessor#process(String, List, String)} returns
+ * {@link CountryZonesProcessor#process(String, String, List, String)} returns
* {@link CountryZonesProcessor#HALT} or it throws an exception.
*/
private void processXml(CountryZonesProcessor processor)
@@ -245,14 +317,14 @@
/*
* The expected XML structure is:
- * <timezones>
+ * <timezones ianaversion="2017b">
* <countryzones>
- * <country code="us">
+ * <country code="us" default="America/New_York">
* <id>America/New_York"</id>
* ...
* <id>America/Los_Angeles</id>
* </country>
- * <country code="gb">
+ * <country code="gb" default="Europe/London">
* <id>Europe/London</id>
* </country>
* </countryzones>
@@ -261,6 +333,9 @@
findRequiredStartTag(parser, TIMEZONES_ELEMENT);
+ // We do not require the ianaversion attribute be present. It is metadata that helps
+ // with versioning but is not required.
+
// There is only one expected sub-element <countryzones> in the format currently, skip
// over anything before it.
findRequiredStartTag(parser, COUNTRY_ZONES_ELEMENT);
@@ -298,10 +373,16 @@
throw new XmlPullParserException(
"Unable to find country code: " + parser.getPositionDescription());
}
+ String defaultTimeZoneId = parser.getAttributeValue(
+ null /* namespace */, DEFAULT_TIME_ZONE_ID_ATTRIBUTE);
+ if (defaultTimeZoneId == null || defaultTimeZoneId.isEmpty()) {
+ throw new XmlPullParserException("Unable to find default time zone ID: "
+ + parser.getPositionDescription());
+ }
String debugInfo = parser.getPositionDescription();
List<String> timeZoneIds = parseZoneIds(parser);
- if (processor.process(code, timeZoneIds, debugInfo)
+ if (processor.process(code, defaultTimeZoneId, timeZoneIds, debugInfo)
== CountryZonesProcessor.HALT) {
return CountryZonesProcessor.HALT;
}
@@ -311,7 +392,7 @@
checkOnEndTag(parser, COUNTRY_ELEMENT);
}
- return CountryZonesExtractor.CONTINUE;
+ return CountryZonesProcessor.CONTINUE;
}
private static List<String> parseZoneIds(XmlPullParser parser)
@@ -479,79 +560,80 @@
* should stop (but without considering this an error). Problems with parser are reported as
* an exception.
*/
- boolean process(String countryCode, List<String> timeZoneIds, String debugInfo)
- throws XmlPullParserException;
+ boolean process(String countryIso, String defaultTimeZoneId, List<String> timeZoneIds,
+ String debugInfo) throws XmlPullParserException;
}
/**
- * Validates <countryzones> elements. To be valid the country ISO code must be unique
- * and it must not be empty.
+ * Validates <countryzones> elements. Intended to be used before a proposed installation
+ * of new data. To be valid the country ISO code must be normalized, unique, the default time
+ * zone ID must be one of the time zones IDs and the time zone IDs list must not be empty. The
+ * IDs themselves are not checked against other data to see if they are recognized because other
+ * classes will not have been updated with the associated new time zone data yet and so will not
+ * be aware of newly added IDs.
*/
private static class CountryZonesValidator implements CountryZonesProcessor {
private final Set<String> knownCountryCodes = new HashSet<>();
@Override
- public boolean process(String countryCode, List<String> timeZoneIds, String debugInfo)
- throws XmlPullParserException {
- if (knownCountryCodes.contains(countryCode)) {
- throw new XmlPullParserException("Second entry for country code: " + countryCode
+ public boolean process(String countryIso, String defaultTimeZoneId,
+ List<String> timeZoneIds, String debugInfo) throws XmlPullParserException {
+ if (!normalizeCountryIso(countryIso).equals(countryIso)) {
+ throw new XmlPullParserException("Country code: " + countryIso
+ + " is not normalized at " + debugInfo);
+ }
+ if (knownCountryCodes.contains(countryIso)) {
+ throw new XmlPullParserException("Second entry for country code: " + countryIso
+ " at " + debugInfo);
}
if (timeZoneIds.isEmpty()) {
- throw new XmlPullParserException("No time zone IDs for country code: " + countryCode
+ throw new XmlPullParserException("No time zone IDs for country code: " + countryIso
+ " at " + debugInfo);
}
-
- // We don't validate the zone IDs - they may be new and we can't easily check them
- // against other timezone data that may be associated with this file.
-
- knownCountryCodes.add(countryCode);
+ if (!timeZoneIds.contains(defaultTimeZoneId)) {
+ throw new XmlPullParserException("defaultTimeZoneId for country code: "
+ + countryIso + " is not one of the zones " + timeZoneIds + " at "
+ + debugInfo);
+ }
+ knownCountryCodes.add(countryIso);
return CONTINUE;
}
}
/**
- * Extracts the zones associated with a country code, halting when the country code is matched
- * and making them available via {@link #getMatchedZones()}.
+ * Extracts <em>validated</em> time zones information associated with a specific country code.
+ * Processing is halted when the country code is matched and the validated result is also made
+ * available via {@link #getValidatedCountryTimeZones()}.
*/
- private static class CountryZonesExtractor implements CountryZonesProcessor {
+ private static class SelectiveCountryTimeZonesExtractor implements CountryZonesProcessor {
private final String countryCodeToMatch;
- private List<TimeZone> matchedZones;
+ private CountryTimeZones validatedCountryTimeZones;
- private CountryZonesExtractor(String countryCodeToMatch) {
+ private SelectiveCountryTimeZonesExtractor(String countryCodeToMatch) {
this.countryCodeToMatch = countryCodeToMatch;
}
@Override
- public boolean process(String countryCode, List<String> timeZoneIds, String debugInfo) {
- if (!countryCodeToMatch.equals(countryCode)) {
+ public boolean process(String countryIso, String defaultTimeZoneId,
+ List<String> countryTimeZoneIds, String debugInfo) {
+ countryIso = normalizeCountryIso(countryIso);
+ if (!countryCodeToMatch.equals(countryIso)) {
return CONTINUE;
}
+ validatedCountryTimeZones = createValidatedCountryTimeZones(countryIso,
+ defaultTimeZoneId, countryTimeZoneIds, debugInfo);
- List<TimeZone> timeZones = new ArrayList<>();
- for (String zoneIdString : timeZoneIds) {
- TimeZone tz = TimeZone.getTimeZone(zoneIdString);
- if (tz.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
- System.logW("Skipping invalid zone: " + zoneIdString + " at " + debugInfo);
- } else {
- // The zone is frozen to prevent mutation by callers.
- timeZones.add(tz.freeze());
- }
- }
- matchedZones = Collections.unmodifiableList(timeZones);
return HALT;
}
/**
- * Returns the matched zones, or {@code null} if there were no matches. Unknown zone IDs are
- * ignored so the list can be empty if there were no zones or the zone IDs were not
- * recognized.
+ * Returns the CountryTimeZones that matched, or {@code null} if there were no matches.
*/
- List<TimeZone> getMatchedZones() {
- return matchedZones;
+ CountryTimeZones getValidatedCountryTimeZones() {
+ return validatedCountryTimeZones;
}
}
@@ -577,4 +659,114 @@
return () -> new StringReader(xml);
}
}
+
+ /**
+ * Information about a country's time zones.
+ */
+ // VisibleForTesting
+ public static class CountryTimeZones {
+ private final String countryIso;
+ private final String defaultTimeZoneId;
+ private final List<String> timeZoneIds;
+
+ // Memoized frozen ICU TimeZone objects for the timeZoneIds.
+ private List<TimeZone> timeZones;
+
+ public CountryTimeZones(String countryIso, String defaultTimeZoneId,
+ List<String> timeZoneIds) {
+ this.countryIso = countryIso;
+ this.defaultTimeZoneId = defaultTimeZoneId;
+ // Create a defensive copy of the IDs list.
+ this.timeZoneIds = Collections.unmodifiableList(new ArrayList<>(timeZoneIds));
+ }
+
+ public String getCountryIso() {
+ return countryIso;
+ }
+
+ /**
+ * Returns the default time zone ID for a country. Can return null in extreme cases when
+ * invalid data is found.
+ */
+ public String getDefaultTimeZoneId() {
+ return defaultTimeZoneId;
+ }
+
+ /**
+ * Returns an ordered list of time zone IDs for a country in an undefined but "priority"
+ * order for a country. The list can be empty if there were no zones configured or the
+ * configured zone IDs were not recognized.
+ */
+ public List<String> getTimeZoneIds() {
+ return timeZoneIds;
+ }
+
+ /**
+ * Returns an ordered list of time zones for a country in an undefined but "priority"
+ * order for a country. The list can be empty if there were no zones configured or the
+ * configured zone IDs were not recognized.
+ */
+ public synchronized List<TimeZone> getTimeZones() {
+ if (timeZones == null) {
+ ArrayList<TimeZone> mutableList = new ArrayList<>(timeZoneIds.size());
+ for (String timeZoneId : timeZoneIds) {
+ TimeZone timeZone = getValidFrozenTimeZoneOrNull(timeZoneId);
+ // This shouldn't happen given the validation that takes place in
+ // createValidatedCountryTimeZones().
+ if (timeZone == null) {
+ System.logW("Skipping invalid zone: " + timeZoneId);
+ continue;
+ }
+ mutableList.add(timeZone);
+ }
+ timeZones = Collections.unmodifiableList(mutableList);
+ }
+ return timeZones;
+ }
+
+ private static TimeZone getValidFrozenTimeZoneOrNull(String timeZoneId) {
+ TimeZone timeZone = TimeZone.getFrozenTimeZone(timeZoneId);
+ if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
+ return null;
+ }
+ return timeZone;
+ }
+ }
+
+ private static String normalizeCountryIso(String countryIso) {
+ // Lowercase ASCII is normalized for the purposes of the input files and the code in this
+ // class.
+ return countryIso.toLowerCase(Locale.US);
+ }
+
+ // VisibleForTesting
+ public static CountryTimeZones createValidatedCountryTimeZones(String countryIso,
+ String defaultTimeZoneId, List<String> countryTimeZoneIds, String debugInfo) {
+
+ // We rely on ZoneInfoDB to tell us what the known valid time zone IDs are. ICU may
+ // recognize more but we want to be sure that zone IDs can be used with java.util as well as
+ // android.icu and ICU is expected to have a superset.
+ String[] validTimeZoneIdsArray = ZoneInfoDB.getInstance().getAvailableIDs();
+ HashSet<String> validTimeZoneIdsSet = new HashSet<>(Arrays.asList(validTimeZoneIdsArray));
+ List<String> validCountryTimeZoneIds = new ArrayList<>();
+ for (String countryTimeZoneId : countryTimeZoneIds) {
+ if (!validTimeZoneIdsSet.contains(countryTimeZoneId)) {
+ System.logW("Skipping invalid zone: " + countryTimeZoneId + " at " + debugInfo);
+ } else {
+ validCountryTimeZoneIds.add(countryTimeZoneId);
+ }
+ }
+
+ // We don't get too strict at runtime about whether the defaultTimeZoneId must be
+ // one of the country's time zones because this is the data we have to use (we also
+ // assume the data was validated by earlier steps). The default time zone ID must just
+ // be a recognized zone ID: if it's not valid we leave it null.
+ if (!validTimeZoneIdsSet.contains(defaultTimeZoneId)) {
+ System.logW("Invalid default time zone ID: " + defaultTimeZoneId
+ + " at " + debugInfo);
+ defaultTimeZoneId = null;
+ }
+
+ return new CountryTimeZones(countryIso, defaultTimeZoneId, validCountryTimeZoneIds);
+ }
}
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index c392553..bd8c116 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -21,6 +21,7 @@
#include "log/log.h"
#include <nativehelper/JniConstants.h>
+#include "nativehelper/JniConstants-priv.h"
#include <nativehelper/ScopedLocalFrame.h>
// DalvikVM calls this on startup, so we can statically register all our native methods.
@@ -30,6 +31,7 @@
ALOGE("JavaVM::GetEnv() failed");
abort();
}
+ JniConstants::init(env);
ScopedLocalFrame localFrame(env);
@@ -71,4 +73,8 @@
#define UNREGISTER(FN) extern void FN(JNIEnv*); FN(env)
UNREGISTER(unregister_libcore_icu_ICU);
#undef UNREGISTER
+
+ // Ensure that libnativehelper caching is invalidated, in case a new runtime is to be brought
+ // up later.
+ android::ClearJniConstantsCache();
}
diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp
index d836164..48f1ba2 100644
--- a/luni/src/main/native/libcore_icu_ICU.cpp
+++ b/luni/src/main/native/libcore_icu_ICU.cpp
@@ -423,7 +423,7 @@
setCharField(env, obj, "groupingSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kGroupingSeparatorSymbol));
setCharField(env, obj, "patternSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kPatternSeparatorSymbol));
setStringField(env, obj, "percent", dfs.getSymbol(icu::DecimalFormatSymbols::kPercentSymbol));
- setCharField(env, obj, "perMill", dfs.getSymbol(icu::DecimalFormatSymbols::kPerMillSymbol));
+ setStringField(env, obj, "perMill", dfs.getSymbol(icu::DecimalFormatSymbols::kPerMillSymbol));
setCharField(env, obj, "monetarySeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kMonetarySeparatorSymbol));
setStringField(env, obj, "minusSign", dfs.getSymbol(icu::DecimalFormatSymbols:: kMinusSignSymbol));
setStringField(env, obj, "exponentSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kExponentialSymbol));
diff --git a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
index 29c07e2..ada6700 100644
--- a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
+++ b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
@@ -115,32 +115,8 @@
}
}
-static jstring TimeZoneNames_getExemplarLocation(JNIEnv* env, jclass, jstring javaLocaleName, jstring javaTz) {
- ScopedIcuLocale icuLocale(env, javaLocaleName);
- if (!icuLocale.valid()) {
- return NULL;
- }
-
- UErrorCode status = U_ZERO_ERROR;
- std::unique_ptr<icu::TimeZoneNames> names(icu::TimeZoneNames::createInstance(icuLocale.locale(), status));
- if (maybeThrowIcuException(env, "TimeZoneNames::createInstance", status)) {
- return NULL;
- }
-
- ScopedJavaUnicodeString tz(env, javaTz);
- if (!tz.valid()) {
- return NULL;
- }
-
- icu::UnicodeString s;
- const UDate now(icu::Calendar::getNow());
- names->getDisplayName(tz.unicodeString(), UTZNM_EXEMPLAR_LOCATION, now, s);
- return jniCreateString(env, s.getBuffer(), s.length());
-}
-
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(TimeZoneNames, fillZoneStrings, "(Ljava/lang/String;[[Ljava/lang/String;)V"),
- NATIVE_METHOD(TimeZoneNames, getExemplarLocation, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
};
void register_libcore_icu_TimeZoneNames(JNIEnv* env) {
jniRegisterNativeMethods(env, "libcore/icu/TimeZoneNames", gMethods, NELEM(gMethods));
diff --git a/luni/src/module/java/module-info.java b/luni/src/module/java/module-info.java
index acb4dc3..7f11b17 100644
--- a/luni/src/module/java/module-info.java
+++ b/luni/src/module/java/module-info.java
@@ -114,6 +114,8 @@
exports javax.xml.parsers;
exports libcore.io;
exports libcore.icu;
+ exports libcore.net;
+ exports libcore.net.event;
exports libcore.net.http;
exports libcore.reflect;
exports libcore.util;
diff --git a/luni/src/test/java/libcore/dalvik/system/SocketTaggingTest.java b/luni/src/test/java/libcore/dalvik/system/SocketTaggingTest.java
deleted file mode 100644
index cc30534..0000000
--- a/luni/src/test/java/libcore/dalvik/system/SocketTaggingTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package libcore.dalvik.system;
-
-import android.system.ErrnoException;
-import android.system.StructStat;
-import dalvik.system.SocketTagger;
-import junit.framework.TestCase;
-
-import java.io.FileDescriptor;
-import java.net.DatagramSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.nio.channels.DatagramChannel;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-public class SocketTaggingTest extends TestCase {
- static final class StatAndDescriptor {
- final int fd;
- final StructStat stat;
-
- StatAndDescriptor(FileDescriptor fd) {
- this.fd = fd.getInt$();
- this.stat = fstat(fd);
- }
-
- @Override
- public String toString() {
- return "[fd=" + fd + ", stat=" + stat + "]";
- }
- }
-
- static class RecordingSocketTagger extends SocketTagger {
- private final Map<Integer, StatAndDescriptor> liveDescriptors = new HashMap<>();
-
- @Override
- public void tag(FileDescriptor socketDescriptor) throws SocketException {
- liveDescriptors.put(socketDescriptor.getInt$(),
- new StatAndDescriptor(socketDescriptor));
- }
-
- @Override
- public void untag(FileDescriptor socketDescriptor) throws SocketException {
- StatAndDescriptor existing = liveDescriptors.remove(socketDescriptor.getInt$());
-
- // We compare the current description of the descriptor with the description
- // we used to tag with and make sure they describe the same file. This helps test
- // whether we untag the socket at the "right" time.
- StructStat current = fstat(socketDescriptor);
- assertEquals(existing.stat.st_dev, current.st_dev);
- assertEquals(existing.stat.st_ino, current.st_ino);
- }
-
- public Map<Integer, StatAndDescriptor> getLiveDescriptors() {
- return liveDescriptors;
- }
- }
-
- private RecordingSocketTagger tagger;
- private SocketTagger original;
-
- private ServerSocketChannel server;
-
- @Override
- public void setUp() throws Exception {
- server = ServerSocketChannel.open();
- server.configureBlocking(false);
- server.bind(null);
-
- original = SocketTagger.get();
- tagger = new RecordingSocketTagger();
- SocketTagger.set(tagger);
- }
-
- @Override
- public void tearDown() {
- SocketTagger.set(original);
- }
-
- public void testSocketChannel() throws Exception {
- SocketChannel sc = SocketChannel.open();
- sc.connect(server.getLocalAddress());
- assertEquals(1, tagger.getLiveDescriptors().size());
-
- sc.close();
-
- assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
- }
-
- public void testServerSocketChannel() throws Exception {
- ServerSocketChannel ssc = ServerSocketChannel.open();
- ssc.bind(null);
- assertEquals(1, tagger.getLiveDescriptors().size());
-
- ssc.close();
-
- assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
- }
-
- public void testDatagramChannel() throws Exception {
- DatagramChannel dc = DatagramChannel.open();
- dc.connect(server.getLocalAddress());
- assertEquals(1, tagger.getLiveDescriptors().size());
-
- dc.close();
-
- assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
- }
-
- public void testSocket() throws Exception {
- Socket s = new Socket();
- s.connect(server.getLocalAddress());
- assertEquals(1, tagger.getLiveDescriptors().size());
-
- s.close();
-
- assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
- }
-
- public void testDatagramSocket() throws Exception {
- DatagramSocket d = new DatagramSocket();
- d.connect(server.getLocalAddress());
- assertEquals(1, tagger.getLiveDescriptors().size());
-
- d.close();
-
- assertEquals(Collections.EMPTY_MAP, tagger.getLiveDescriptors());
- }
-
- private static StructStat fstat(FileDescriptor fd) {
- try {
- return android.system.Os.fstat(fd);
- } catch (ErrnoException e) {
- throw new AssertionError(e);
- }
- }
-}
diff --git a/luni/src/test/java/libcore/icu/ICUTest.java b/luni/src/test/java/libcore/icu/ICUTest.java
index fdc7225..a8e4d59 100644
--- a/luni/src/test/java/libcore/icu/ICUTest.java
+++ b/luni/src/test/java/libcore/icu/ICUTest.java
@@ -16,10 +16,16 @@
package libcore.icu;
+import android.icu.util.TimeZone;
+
import java.text.BreakIterator;
import java.text.Collator;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
+import java.util.Set;
+import libcore.util.TimeZoneFinder;
import libcore.util.ZoneInfoDB;
public class ICUTest extends junit.framework.TestCase {
@@ -258,10 +264,65 @@
/** Confirms that ICU agrees with the rest of libcore about the version of the TZ data in use. */
public void testTimeZoneDataVersion() {
String icu4cTzVersion = ICU.getTZDataVersion();
+
String zoneInfoTzVersion = ZoneInfoDB.getInstance().getVersion();
assertEquals(icu4cTzVersion, zoneInfoTzVersion);
String icu4jTzVersion = android.icu.util.TimeZone.getTZDataVersion();
assertEquals(icu4jTzVersion, zoneInfoTzVersion);
+
+ String tzLookupTzVersion = TimeZoneFinder.getInstance().getIanaVersion();
+ assertEquals(icu4jTzVersion, tzLookupTzVersion);
+ }
+
+ /**
+ * Confirms that ICU can recognize all the time zone IDs used by the ZoneInfoDB data.
+ * ICU's IDs may be a superset.
+ */
+ public void testTimeZoneIdLookup() {
+ String[] zoneInfoDbAvailableIds = ZoneInfoDB.getInstance().getAvailableIDs();
+
+ // ICU has a known set of IDs. We want ANY because we don't want to filter to ICU's canonical
+ // IDs only.
+ Set<String> icuAvailableIds = android.icu.util.TimeZone.getAvailableIDs(
+ TimeZone.SystemTimeZoneType.ANY, null /* region */, null /* rawOffset */);
+
+ List<String> nonIcuAvailableIds = new ArrayList<>();
+ List<String> creationFailureIds = new ArrayList<>();
+ List<String> noCanonicalLookupIds = new ArrayList<>();
+ List<String> nonSystemIds = new ArrayList<>();
+ for (String zoneInfoDbId : zoneInfoDbAvailableIds) {
+
+ if (zoneInfoDbId.equals("Asia/Hanoi")) {
+ // Known ICU lookup issue: http://b/30277331
+ continue;
+ }
+
+ if (!icuAvailableIds.contains(zoneInfoDbId)) {
+ nonIcuAvailableIds.add(zoneInfoDbId);
+ }
+
+ boolean[] isSystemId = new boolean[1];
+ String canonicalId = android.icu.util.TimeZone.getCanonicalID(zoneInfoDbId, isSystemId);
+ if (canonicalId == null) {
+ noCanonicalLookupIds.add(zoneInfoDbId);
+ }
+ if (!isSystemId[0]) {
+ nonSystemIds.add(zoneInfoDbId);
+ }
+
+ android.icu.util.TimeZone icuTimeZone = android.icu.util.TimeZone.getTimeZone(zoneInfoDbId);
+ if (icuTimeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
+ creationFailureIds.add(zoneInfoDbId);
+ }
+ }
+ assertTrue("Non-ICU available IDs: " + nonIcuAvailableIds
+ + ", creation failed IDs: " + creationFailureIds
+ + ", non-system IDs: " + nonSystemIds
+ + ", ids without canonical IDs: " + noCanonicalLookupIds,
+ nonIcuAvailableIds.isEmpty()
+ && creationFailureIds.isEmpty()
+ && nonSystemIds.isEmpty()
+ && noCanonicalLookupIds.isEmpty());
}
}
diff --git a/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java b/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java
index 57943b0..c2f8312 100644
--- a/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java
+++ b/luni/src/test/java/libcore/icu/TimeZoneNamesTest.java
@@ -59,11 +59,4 @@
assertTrue(TimeZoneNames.forLocale(l) != null);
}
}
-
- public void test_getExemplarLocation() throws Exception {
- assertEquals("Moscow", TimeZoneNames.getExemplarLocation("en_US", "Europe/Moscow"));
- assertEquals("Moskau", TimeZoneNames.getExemplarLocation("de_DE", "Europe/Moscow"));
- assertEquals("Seoul", TimeZoneNames.getExemplarLocation("en_US", "Asia/Seoul"));
- assertEquals("서울", TimeZoneNames.getExemplarLocation("ko_KR", "Asia/Seoul"));
- }
}
diff --git a/luni/src/test/java/libcore/java/io/FileTest.java b/luni/src/test/java/libcore/java/io/FileTest.java
index 9226e02..3705f2b 100644
--- a/luni/src/test/java/libcore/java/io/FileTest.java
+++ b/luni/src/test/java/libcore/java/io/FileTest.java
@@ -393,4 +393,25 @@
fail();
} catch (InvalidPathException expected) {}
}
+
+ // http://b/62301183
+ public void test_canonicalCachesAreOff() throws Exception {
+ File tempDir = createTemporaryDirectory();
+ File f1 = new File(tempDir, "testCannonCachesOff1");
+ f1.createNewFile();
+ File f2 = new File(tempDir, "testCannonCachesOff2");
+ f2.createNewFile();
+ File symlinkFile = new File(tempDir, "symlink");
+
+ // Create a symlink from symlink to f1 and populate canonical path cache
+ assertEquals(0, Runtime.getRuntime().exec("ln -s " + f1.getAbsolutePath() + " " + symlinkFile.getAbsolutePath()).waitFor());
+ assertEquals(symlinkFile.getCanonicalPath(), f1.getCanonicalPath());
+
+ // Remove it and replace it with a symlink to f2 (using java File/Files would flush caches).
+ assertEquals(0, Runtime.getRuntime().exec("rm " + symlinkFile.getAbsolutePath()).waitFor());
+ assertEquals(0, Runtime.getRuntime().exec("ln -s " + f2.getAbsolutePath() + " " + symlinkFile.getAbsolutePath()).waitFor());
+
+ // Did we cache canonical path results? hope not!
+ assertEquals(symlinkFile.getCanonicalPath(), f2.getCanonicalPath());
+ }
}
diff --git a/luni/src/test/java/libcore/java/net/FtpURLConnectionTest.java b/luni/src/test/java/libcore/java/net/FtpURLConnectionTest.java
index 2842a0a..74948f6 100644
--- a/luni/src/test/java/libcore/java/net/FtpURLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/FtpURLConnectionTest.java
@@ -44,15 +44,11 @@
import java.util.Locale;
import java.util.Objects;
import java.util.Random;
-import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
- import sun.net.ftp.FtpLoginException;
+import sun.net.ftp.FtpLoginException;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -253,6 +249,34 @@
}
}
+ // http://b/35784677
+ public void testCRLFInUserinfo() throws Exception {
+ int serverPort = fakeFtpServer.getServerControlPort();
+ List<String> encodedUserInfos = Arrays.asList(
+ // '\r\n' in the username with password
+ "user%0D%0Acommand:password",
+ // '\r\n' in the password
+ "user:password%0D%0Acommand",
+ // just '\n' in the password
+ "user:password%0Acommand",
+ // just '\n' in the username
+ "user%0Acommand:password"
+ );
+ for (String encodedUserInfo : encodedUserInfos) {
+ String urlString = String.format(Locale.US, "ftp://%s@%s:%s/%s",
+ encodedUserInfo, SERVER_HOSTNAME, serverPort, FILE_PATH);
+ try {
+ new URL(urlString).openConnection().connect();
+ fail("Connection shouldn't have succeeded: " + urlString);
+ } catch (FtpLoginException expected) {
+ // The original message "Illegal carriage return" gets lost
+ // where FtpURLConnection.connect() translates the
+ // original FtpProtocolException into FtpLoginException.
+ assertEquals("Invalid username/password", expected.getMessage());
+ }
+ }
+ }
+
private InputStream openFileSystemContents(String fileName) throws IOException {
String fullFileName = VALID_USER_HOME_DIR + "/" + fileName;
FileEntry entry = (FileEntry) fileSystem.getEntry(fullFileName);
@@ -279,49 +303,6 @@
}
}
- // http://b/35784677
- public void testCRLFInUserinfo() throws Exception {
- List<String> encodedUserInfos = Arrays.asList(
- // '\r\n' in the username with password
- "user%0D%0Acommand:password",
- // '\r\n' in the password
- "user:password%0D%0Acommand",
- // just '\n' in the password
- "user:password%0Acommand",
- // just '\n' in the username
- "user%0Acommand:password"
- );
- for (String encodedUserInfo : encodedUserInfos) {
- ExecutorService executor = Executors.newSingleThreadExecutor();
- ServerSocket mockFtpServerSocket = new ServerSocket(0);
- Future<Void> future = executor.submit(new Callable<Void>() {
- @Override public Void call() throws Exception {
- Socket clientSocket = mockFtpServerSocket.accept();
- clientSocket.getOutputStream().write("220 o/".getBytes());
- clientSocket.close();
- return null;
- }
- });
- executor.shutdown();
-
- String urlString = String.format(Locale.US, "ftp://%s@%s:%s/%s",
- encodedUserInfo, SERVER_HOSTNAME, mockFtpServerSocket.getLocalPort(), FILE_PATH);
- try {
- new URL(urlString).openConnection().connect();
- fail("Connection shouldn't have succeeded: " + urlString);
- } catch (FtpLoginException expected) {
- // The original message "Illegal carriage return" gets lost
- // where FtpURLConnection.connect() translates the
- // original FtpProtocolException into FtpLoginException.
- assertEquals("Invalid username/password", expected.getMessage());
- }
-
- // Cleanup
- future.get();
- mockFtpServerSocket.close();
- }
- }
-
private URL getFileUrlWithCredentials(String user, String password, String filePath) {
Objects.requireNonNull(user);
Objects.requireNonNull(filePath);
diff --git a/luni/src/test/java/libcore/java/text/DecimalFormatTest.java b/luni/src/test/java/libcore/java/text/DecimalFormatTest.java
index 60fafac..766a8d3 100644
--- a/luni/src/test/java/libcore/java/text/DecimalFormatTest.java
+++ b/luni/src/test/java/libcore/java/text/DecimalFormatTest.java
@@ -434,7 +434,7 @@
// Confirm ICU and java.text disagree.
// java.text doesn't localize PerMill and fallback to default char U+2030
// when PerMill in that locale has more than one visible characters.
- locale = new Locale("en_US_POSIX");
+ locale = Locale.forLanguageTag("en-US-POSIX");
{
android.icu.text.DecimalFormat df = new android.icu.text.DecimalFormat(pattern,
new android.icu.text.DecimalFormatSymbols(locale));
diff --git a/luni/src/test/java/libcore/util/TimeZoneFinderTest.java b/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
index 0b31c9a..f675150 100644
--- a/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
+++ b/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
@@ -30,6 +30,8 @@
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -39,6 +41,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class TimeZoneFinderTest {
@@ -96,16 +99,16 @@
@Test
public void createInstanceWithFallback() throws Exception {
- String validXml1 = "<timezones>\n"
+ String validXml1 = "<timezones ianaversion=\"2017c\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n";
- String validXml2 = "<timezones>\n"
+ String validXml2 = "<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/Paris</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
@@ -121,26 +124,36 @@
TimeZoneFinder file1ThenFile2 =
TimeZoneFinder.createInstanceWithFallback(validFile1, validFile2);
+ assertEquals("2017c", file1ThenFile2.getIanaVersion());
+ assertEquals(list("Europe/London"), file1ThenFile2.lookupTimeZoneIdsByCountry("gb"));
assertZonesEqual(zones("Europe/London"), file1ThenFile2.lookupTimeZonesByCountry("gb"));
TimeZoneFinder missingFileThenFile1 =
TimeZoneFinder.createInstanceWithFallback(missingFile, validFile1);
+ assertEquals("2017c", missingFileThenFile1.getIanaVersion());
+ assertEquals(list("Europe/London"), missingFileThenFile1.lookupTimeZoneIdsByCountry("gb"));
assertZonesEqual(zones("Europe/London"),
missingFileThenFile1.lookupTimeZonesByCountry("gb"));
TimeZoneFinder file2ThenFile1 =
TimeZoneFinder.createInstanceWithFallback(validFile2, validFile1);
+ assertEquals("2017b", file2ThenFile1.getIanaVersion());
+ assertEquals(list("Europe/Paris"), file2ThenFile1.lookupTimeZoneIdsByCountry("gb"));
assertZonesEqual(zones("Europe/Paris"), file2ThenFile1.lookupTimeZonesByCountry("gb"));
// We assume the file has been validated so an invalid file is not checked ahead of time.
// We will find out when we look something up.
TimeZoneFinder invalidThenValid =
TimeZoneFinder.createInstanceWithFallback(invalidFile, validFile1);
+ assertNull(invalidThenValid.getIanaVersion());
+ assertNull(invalidThenValid.lookupTimeZoneIdsByCountry("gb"));
assertNull(invalidThenValid.lookupTimeZonesByCountry("gb"));
// This is not a normal case: It would imply a define shipped without a file in /system!
TimeZoneFinder missingFiles =
TimeZoneFinder.createInstanceWithFallback(missingFile, missingFile);
+ assertNull(missingFiles.getIanaVersion());
+ assertNull(missingFiles.lookupTimeZoneIdsByCountry("gb"));
assertNull(missingFiles.lookupTimeZonesByCountry("gb"));
}
@@ -156,12 +169,12 @@
@Test
public void xmlParsing_missingCountryZones() throws Exception {
- checkValidateThrowsParserException("<timezones></timezones>\n");
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\"></timezones>\n");
}
@Test
public void xmlParsing_noCountriesOk() throws Exception {
- validate("<timezones>\n"
+ validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
@@ -169,167 +182,177 @@
@Test
public void xmlParsing_unexpectedComments() throws Exception {
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <!-- This is a comment -->"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
// This is a crazy comment, but also helps prove that TEXT nodes are coalesced by the
// parser.
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/<!-- Don't freak out! -->London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
}
@Test
public void xmlParsing_unexpectedElementsIgnored() throws Exception {
String unexpectedElement = "<unexpected-element>\n<a /></unexpected-element>\n";
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " " + unexpectedElement
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
+ " " + unexpectedElement
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " " + unexpectedElement
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " " + unexpectedElement
+ " <id>Europe/Paris</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London", "Europe/Paris"),
- finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London", "Europe/Paris"),
+ finder.lookupTimeZoneIdsByCountry("gb"));
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " " + unexpectedElement
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
// This test is important because it ensures we can extend the format in future with
// more information.
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ " " + unexpectedElement
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
}
@Test
public void xmlParsing_unexpectedTextIgnored() throws Exception {
String unexpectedText = "unexpected-text";
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " " + unexpectedText
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
+ " " + unexpectedText
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " " + unexpectedText
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
- finder = validate("<timezones>\n"
+ finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " " + unexpectedText
+ " <id>Europe/Paris</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
- assertZonesEqual(zones("Europe/London", "Europe/Paris"),
- finder.lookupTimeZonesByCountry("gb"));
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ assertEquals(list("Europe/London", "Europe/Paris"),
+ finder.lookupTimeZoneIdsByCountry("gb"));
}
@Test
public void xmlParsing_truncatedInput() throws Exception {
- checkValidateThrowsParserException("<timezones>\n");
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n");
- checkValidateThrowsParserException("<timezones>\n"
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n");
- checkValidateThrowsParserException("<timezones>\n"
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n");
+ + " <country code=\"gb\" default=\"Europe/London\">\n");
- checkValidateThrowsParserException("<timezones>\n"
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n");
- checkValidateThrowsParserException("<timezones>\n"
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n");
- checkValidateThrowsParserException("<timezones>\n"
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n");
@@ -337,9 +360,9 @@
@Test
public void xmlParsing_unexpectedChildInTimeZoneIdThrows() throws Exception {
- checkValidateThrowsParserException("<timezones>\n"
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id><unexpected-element /></id>\n"
+ " </country>\n"
+ " </countryzones>\n"
@@ -348,22 +371,34 @@
@Test
public void xmlParsing_unknownTimeZoneIdIgnored() throws Exception {
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Unknown_Id</id>\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
}
@Test
public void xmlParsing_missingCountryCode() throws Exception {
- checkValidateThrowsParserException("<timezones>\n"
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country>\n"
+ + " <country default=\"Europe/London\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ }
+
+ @Test
+ public void xmlParsing_missingDefault() throws Exception {
+ checkValidateThrowsParserException("<timezones ianaversion=\"2017b\">\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
@@ -372,18 +407,19 @@
@Test
public void xmlParsing_unknownCountryReturnsNull() throws Exception {
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
+ " </countryzones>\n"
+ "</timezones>\n");
+ assertNull(finder.lookupTimeZoneIdsByCountry("gb"));
assertNull(finder.lookupTimeZonesByCountry("gb"));
}
@Test
public void lookupTimeZonesByCountry_structuresAreImmutable() throws Exception {
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"gb\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
@@ -394,14 +430,91 @@
assertImmutableList(gbList);
assertImmutableTimeZone(gbList.get(0));
+ // Check country code normalization works too.
+ assertEquals(1, finder.lookupTimeZonesByCountry("GB").size());
+
assertNull(finder.lookupTimeZonesByCountry("unknown"));
}
@Test
- public void lookupTimeZoneByCountryAndOffset_unknownCountry() throws Exception {
- TimeZoneFinder finder = validate("<timezones>\n"
+ public void lookupTimeZoneIdsByCountry_structuresAreImmutable() throws Exception {
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"xx\">\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+
+ List<String> gbList = finder.lookupTimeZoneIdsByCountry("gb");
+ assertEquals(1, gbList.size());
+ assertImmutableList(gbList);
+
+ // Check country code normalization works too.
+ assertEquals(1, finder.lookupTimeZoneIdsByCountry("GB").size());
+
+ assertNull(finder.lookupTimeZoneIdsByCountry("unknown"));
+ }
+
+ @Test
+ public void lookupDefaultTimeZoneIdByCountry() throws Exception {
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+
+ // Check country code normalization works too.
+ assertEquals("Europe/London", finder.lookupDefaultTimeZoneIdByCountry("GB"));
+ }
+
+ /**
+ * At runtime we don't validate too much since there's nothing we can do if the data is
+ * incorrect.
+ */
+ @Test
+ public void lookupDefaultTimeZoneIdByCountry_notCountryTimeZoneButValid() throws Exception {
+ String xml = "<timezones ianaversion=\"2017b\">\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\" default=\"America/New_York\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n";
+ // validate() should fail because America/New_York is not one of the "gb" zones listed.
+ checkValidateThrowsParserException(xml);
+
+ // But it should still work at runtime.
+ TimeZoneFinder finder = TimeZoneFinder.createInstanceForTests(xml);
+ assertEquals("America/New_York", finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ }
+
+ @Test
+ public void lookupDefaultTimeZoneIdByCountry_invalidDefault() throws Exception {
+ String xml = "<timezones ianaversion=\"2017b\">\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\" default=\"Moon/Tranquility_Base\">\n"
+ + " <id>Europe/London</id>\n"
+ + " <id>Moon/Tranquility_Base</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n";
+ // validate() should pass because the IDs all match.
+ TimeZoneFinder finder = validate(xml);
+
+ // But "Moon/Tranquility_Base" is not a valid time zone ID so should not be used.
+ assertNull(finder.lookupDefaultTimeZoneIdByCountry("gb"));
+ }
+
+ @Test
+ public void lookupTimeZoneByCountryAndOffset_unknownCountry() throws Exception {
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ + " <countryzones>\n"
+ + " <country code=\"xx\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
@@ -412,6 +525,11 @@
finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
true /* isDst */, WHEN_DST, null /* bias */));
+ // Check country code normalization works too.
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("XX", LONDON_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_DST, null /* bias */));
+
// Test with an unknown country.
String unknownCountryCode = "yy";
assertNull(finder.lookupTimeZoneByCountryAndOffset(unknownCountryCode,
@@ -423,9 +541,9 @@
@Test
public void lookupTimeZoneByCountryAndOffset_oneCandidate() throws Exception {
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"xx\">\n"
+ + " <country code=\"xx\" default=\"Europe/London\">\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
+ " </countryzones>\n"
@@ -492,9 +610,9 @@
@Test
public void lookupTimeZoneByCountryAndOffset_multipleNonOverlappingCandidates()
throws Exception {
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"xx\">\n"
+ + " <country code=\"xx\" default=\"Europe/London\">\n"
+ " <id>America/New_York</id>\n"
+ " <id>Europe/London</id>\n"
+ " </country>\n"
@@ -563,9 +681,9 @@
public void lookupTimeZoneByCountryAndOffset_multipleOverlappingCandidates() throws Exception {
// Three zones that have the same offset for some of the year. Europe/London changes
// offset WHEN_DST, the others do not.
- TimeZoneFinder finder = validate("<timezones>\n"
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"2017b\">\n"
+ " <countryzones>\n"
- + " <country code=\"xx\">\n"
+ + " <country code=\"xx\" default=\"Europe/London\">\n"
+ " <id>Atlantic/Reykjavik</id>\n"
+ " <id>Europe/London</id>\n"
+ " <id>Etc/UTC</id>\n"
@@ -659,18 +777,93 @@
// Android uses lower case, IANA uses upper.
countryCode = countryCode.toLowerCase();
- List<String> ianaZoneIds = countryEntry.getValue().stream().sorted()
- .collect(Collectors.toList());
+ List<String> androidZoneIds = timeZoneFinder.lookupTimeZoneIdsByCountry(countryCode);
List<TimeZone> androidZones = timeZoneFinder.lookupTimeZonesByCountry(countryCode);
- List<String> androidZoneIds =
- androidZones.stream().map(TimeZone::getID).sorted()
- .collect(Collectors.toList());
+ List<String> androidIdsFromZones =
+ androidZones.stream().map(TimeZone::getID).collect(Collectors.toList());
+ assertEquals("lookupTimeZonesByCountry and lookupTimeZoneIdsByCountry differ for "
+ + countryCode, androidZoneIds, androidIdsFromZones);
+ Collection<String> ianaZoneIds = countryEntry.getValue();
assertEquals("Android zones for " + countryCode + " do not match IANA data",
- ianaZoneIds, androidZoneIds);
+ sort(ianaZoneIds), sort(androidZoneIds));
}
}
+ @Test
+ public void xmlParsing_missingIanaVersionAttribute() throws Exception {
+ // The <timezones> element will typically have an ianaversion attribute, but it's not
+ // required for parsing.
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\" default=\"Europe/London\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertEquals(list("Europe/London"), finder.lookupTimeZoneIdsByCountry("gb"));
+
+ assertNull(finder.getIanaVersion());
+ }
+
+ @Test
+ public void getIanaVersion() throws Exception {
+ final String expectedIanaVersion = "2017b";
+
+ TimeZoneFinder finder = validate("<timezones ianaversion=\"" + expectedIanaVersion + "\">\n"
+ + " <countryzones>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertEquals(expectedIanaVersion, finder.getIanaVersion());
+ }
+
+ @Test
+ public void createValidatedCountryTimeZones_filtersBadIds() throws Exception {
+ String countryIso = "iso";
+ String knownTimeZoneId1 = "Europe/London";
+ String knownTimeZoneId2 = "America/Los_Angeles";
+ String knownTimeZoneId3 = "America/New_York";
+ String unknownTimeZoneId = "Moon/Tranquility_Base";
+
+ List<String> countryZoneIds = list(
+ knownTimeZoneId1, knownTimeZoneId2, unknownTimeZoneId, knownTimeZoneId3);
+ TimeZoneFinder.CountryTimeZones countryTimeZones =
+ TimeZoneFinder.createValidatedCountryTimeZones(countryIso, knownTimeZoneId1,
+ countryZoneIds, "debugInfoIgnored");
+
+ assertEquals(countryIso, countryTimeZones.getCountryIso());
+
+ assertEquals(knownTimeZoneId1, countryTimeZones.getDefaultTimeZoneId());
+ assertEquals(knownTimeZoneId1, countryTimeZones.getDefaultTimeZoneId());
+
+ // Validation should have filtered the unknown ID.
+ String[] expectedTimeZoneIds = { knownTimeZoneId1, knownTimeZoneId2, knownTimeZoneId3 };
+ assertEquals(list(expectedTimeZoneIds), countryTimeZones.getTimeZoneIds());
+ List<TimeZone> timeZones = countryTimeZones.getTimeZones();
+ for (int i = 0; i < timeZones.size(); i++) {
+ TimeZone timeZone = timeZones.get(i);
+ assertEquals(expectedTimeZoneIds[i], timeZone.getID());
+ assertTrue(timeZone.isFrozen());
+ }
+ }
+
+ @Test
+ public void createValidatedCountryTimeZones_filtersBadDefaultId() throws Exception {
+ String countryIso = "iso";
+ String unknownTimeZoneId = "Moon/Tranquility_Base";
+
+ List<String> countryZoneIds = list(unknownTimeZoneId);
+ TimeZoneFinder.CountryTimeZones countryTimeZones =
+ TimeZoneFinder.createValidatedCountryTimeZones(countryIso, unknownTimeZoneId,
+ countryZoneIds, "debugInfoIgnored");
+
+ assertEquals(countryIso, countryTimeZones.getCountryIso());
+
+ assertNull(countryTimeZones.getDefaultTimeZoneId());
+ assertEquals(Collections.emptyList(), countryTimeZones.getTimeZoneIds());
+ assertEquals(Collections.emptyList(), countryTimeZones.getTimeZones());
+ }
+
private void assertImmutableTimeZone(TimeZone timeZone) {
try {
timeZone.setRawOffset(1000);
@@ -679,9 +872,9 @@
}
}
- private static void assertImmutableList(List<TimeZone> timeZones) {
+ private static <X> void assertImmutableList(List<X> list) {
try {
- timeZones.add(null);
+ list.add(null);
fail();
} catch (UnsupportedOperationException expected) {
}
@@ -711,6 +904,15 @@
return timeZoneFinder;
}
+ private static <X> List<X> list(X... values) {
+ return Arrays.asList(values);
+ }
+
+ private static <X> List<X> sort(Collection<X> value) {
+ return value.stream().sorted()
+ .collect(Collectors.toList());
+ }
+
private static List<TimeZone> zones(String... ids) {
return Arrays.stream(ids).map(TimeZone::getTimeZone).collect(Collectors.toList());
}
diff --git a/ojluni/src/main/java/java/io/FileSystem.java b/ojluni/src/main/java/java/io/FileSystem.java
index 4b0260d..86d8fff 100644
--- a/ojluni/src/main/java/java/io/FileSystem.java
+++ b/ojluni/src/main/java/java/io/FileSystem.java
@@ -226,8 +226,11 @@
// Flags for enabling/disabling performance optimizations for file
// name canonicalization
- static boolean useCanonCaches = true;
- static boolean useCanonPrefixCache = true;
+ // Android-changed: Disabled caches for security reasons (b/62301183)
+ //static boolean useCanonCaches = true;
+ //static boolean useCanonPrefixCache = true;
+ static boolean useCanonCaches = false;
+ static boolean useCanonPrefixCache = false;
private static boolean getBooleanProperty(String prop, boolean defaultVal) {
String val = System.getProperty(prop);
diff --git a/ojluni/src/main/java/java/io/RandomAccessFile.java b/ojluni/src/main/java/java/io/RandomAccessFile.java
index df607cb..985f8ed 100755
--- a/ojluni/src/main/java/java/io/RandomAccessFile.java
+++ b/ojluni/src/main/java/java/io/RandomAccessFile.java
@@ -28,6 +28,7 @@
import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;
+import android.system.Os;
import android.system.ErrnoException;
import dalvik.system.CloseGuard;
import libcore.io.IoBridge;
@@ -68,6 +69,12 @@
// BEGIN Android-added: CloseGuard and some helper fields for Android changes in this file.
private final CloseGuard guard = CloseGuard.get();
private final byte[] scratch = new byte[8];
+
+ private static final int FLUSH_NONE = 0;
+ private static final int FLUSH_FSYNC = 1;
+ private static final int FLUSH_FDATASYNC = 2;
+ private int flushAfterWrite = FLUSH_NONE;
+
private int mode;
// END Android-added: CloseGuard and some helper fields for Android changes in this file.
@@ -230,9 +237,17 @@
rw = true;
if (mode.length() > 2) {
if (mode.equals("rws")) {
- imode |= O_SYNC;
+ // Android-changed: For performance reasons, use fsync after each write.
+ // RandomAccessFile.write may result in multiple write syscalls,
+ // O_SYNC/O_DSYNC flags will cause a blocking wait on each syscall. Replacing
+ // them with single fsync/fdatasync call gives better performance with only
+ // minor decrease in reliability.
+ // imode |= O_SYNC;
+ flushAfterWrite = FLUSH_FSYNC;
} else if (mode.equals("rwd")) {
- imode |= O_DSYNC;
+ // Android-changed: For performance reasons, use fdatasync after each write.
+ // imode |= O_DSYNC;
+ flushAfterWrite = FLUSH_FDATASYNC;
} else {
imode = -1;
}
@@ -267,10 +282,29 @@
// BEGIN Android-changed: Use IoBridge.open() instead of open.
fd = IoBridge.open(name, imode);
+ maybeSync();
guard.open("close");
// END Android-changed: Use IoBridge.open() instead of open.
}
+ // BEGIN Android-added: Sync after rws/rwd write
+ private void maybeSync() {
+ if (flushAfterWrite == FLUSH_FSYNC) {
+ try {
+ fd.sync();
+ } catch (IOException e) {
+ // Ignored
+ }
+ } else if (flushAfterWrite == FLUSH_FDATASYNC) {
+ try {
+ Os.fdatasync(fd);
+ } catch (ErrnoException e) {
+ // Ignored
+ }
+ }
+ }
+ // END Android-added: Sync after rws/rwd write
+
/**
* Returns the opaque file descriptor object associated with this
* stream.
@@ -329,7 +363,7 @@
* end-of-file has been reached.
*/
public int read() throws IOException {
- // Android-changed: Implement on top of low-level API, not directly natively.
+ // Android-changed: Implement on top of libcore os API.
// return read0();
return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
}
@@ -342,7 +376,7 @@
* @exception IOException If an I/O error has occurred.
*/
private int readBytes(byte b[], int off, int len) throws IOException {
- // Android-changed: Implement on top of low-level API, not directly natively.
+ // Android-changed: Implement on top of libcore os API.
ioTracker.trackIo(len, IoTracker.Mode.READ);
return IoBridge.read(fd, b, off, len);
}
@@ -485,10 +519,11 @@
* @exception IOException if an I/O error occurs.
*/
public void write(int b) throws IOException {
- // Android-changed: Implement on top of low-level API, not directly natively.
+ // BEGIN Android-changed: Implement on top of libcore os API.
// write0(b);
scratch[0] = (byte) (b & 0xff);
write(scratch, 0, 1);
+ // END Android-changed: Implement on top of libcore os API.
}
/**
@@ -500,9 +535,11 @@
* @exception IOException If an I/O error has occurred.
*/
private void writeBytes(byte b[], int off, int len) throws IOException {
- // Android-changed: Implement on top of low-level API, not directly natively.
+ // BEGIN Android-changed: Implement on top of libcore os API.
ioTracker.trackIo(len, IoTracker.Mode.WRITE);
IoBridge.write(fd, b, off, len);
+ maybeSync();
+ // END Android-changed: Implement on top of libcore os API.
}
/**
@@ -539,12 +576,13 @@
* @exception IOException if an I/O error occurs.
*/
public long getFilePointer() throws IOException {
- // Android-changed: Implement on top of low-level API, not directly natively.
+ // BEGIN Android-changed: Implement on top of libcore os API.
try {
return Libcore.os.lseek(fd, 0L, SEEK_CUR);
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsIOException();
}
+ // END Android-changed: Implement on top of libcore os API.
}
/**
@@ -567,7 +605,7 @@
// throw new IOException("Negative seek offset");
throw new IOException("offset < 0: " + pos);
} else {
- // Android-changed: Implement on top of low-level API, not directly natively.
+ // BEGIN Android-changed: Implement on top of libcore os API.
// seek0(pos);
try {
Libcore.os.lseek(fd, pos, SEEK_SET);
@@ -575,6 +613,7 @@
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsIOException();
}
+ // END Android-changed: Implement on top of libcore os API.
}
}
@@ -585,12 +624,13 @@
* @exception IOException if an I/O error occurs.
*/
public long length() throws IOException {
- // Android-changed: Implement on top of low-level API, not directly natively.
+ // BEGIN Android-changed: Implement on top of libcore os API.
try {
return Libcore.os.fstat(fd).st_size;
} catch (ErrnoException errnoException) {
throw errnoException.rethrowAsIOException();
}
+ // END Android-changed: Implement on top of libcore os API.
}
/**
@@ -613,7 +653,7 @@
* @since 1.2
*/
public void setLength(long newLength) throws IOException {
- // BEGIN Android-changed: Implement on top of low-level API, not directly natively.
+ // BEGIN Android-changed: Implement on top of libcore os API.
if (newLength < 0) {
throw new IllegalArgumentException("newLength < 0");
}
@@ -627,7 +667,8 @@
if (filePointer > newLength) {
seek(newLength);
}
- // END Android-changed: Implement on top of low-level API, not directly natively.
+ maybeSync();
+ // END Android-changed: Implement on top of libcore os API.
}
@@ -654,12 +695,12 @@
closed = true;
}
- // BEGIN Android-changed: Implement on top of low-level API, not directly natively.
+ // BEGIN Android-changed: Implement on top of libcore os API.
if (channel != null && channel.isOpen()) {
channel.close();
}
IoBridge.closeAndSignalBlockedThreads(fd);
- // END Android-changed: Implement on top of low-level API, not directly natively.
+ // END Android-changed: Implement on top of libcore os API.
}
//
diff --git a/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java b/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java
index 85eac6f..e5b0301 100644
--- a/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java
+++ b/ojluni/src/main/java/java/net/AbstractPlainSocketImpl.java
@@ -557,7 +557,6 @@
// Also, close the CloseGuard when the #close is called.
if (!closePending) {
closePending = true;
- SocketTagger.get().untag(fd);
guard.close();
if (fdUseCount == 0) {
diff --git a/ojluni/src/main/java/java/text/DecimalFormatSymbols.java b/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
index a9f11c8..59b8cdc 100644
--- a/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
+++ b/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
@@ -660,12 +660,12 @@
values[0] = String.valueOf(localeData.decimalSeparator);
values[1] = String.valueOf(localeData.groupingSeparator);
values[2] = String.valueOf(localeData.patternSeparator);
- values[3] = String.valueOf(localeData.percent);
+ values[3] = localeData.percent;
values[4] = String.valueOf(localeData.zeroDigit);
values[5] = "#";
values[6] = localeData.minusSign;
values[7] = localeData.exponentSeparator;
- values[8] = String.valueOf(localeData.perMill);
+ values[8] = localeData.perMill;
values[9] = localeData.infinity;
values[10] = localeData.NaN;
data[0] = values;
diff --git a/ojluni/src/main/java/javax/crypto/Cipher.java b/ojluni/src/main/java/javax/crypto/Cipher.java
index dfaf94d..010587d 100644
--- a/ojluni/src/main/java/javax/crypto/Cipher.java
+++ b/ojluni/src/main/java/javax/crypto/Cipher.java
@@ -692,6 +692,7 @@
static final Cipher createCipher(String transformation, Provider provider)
throws NoSuchAlgorithmException, NoSuchPaddingException {
+ Providers.checkBouncyCastleDeprecation(provider, "Cipher", transformation);
String[] tokenizedTransformation = tokenizeTransformation(transformation);
CipherSpiAndProvider cipherSpiAndProvider = null;
diff --git a/ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java b/ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java
index d6c3f76..93b5dc7 100644
--- a/ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java
+++ b/ojluni/src/main/java/javax/net/ssl/SSLSocketFactory.java
@@ -260,6 +260,8 @@
* @throws NullPointerException if {@code s} is {@code null}
*
* @since 1.8
+ *
+ * @hide
*/
public Socket createSocket(Socket s, InputStream consumed,
boolean autoClose) throws IOException {
diff --git a/ojluni/src/main/java/sun/net/ftp/impl/FtpClient.java b/ojluni/src/main/java/sun/net/ftp/impl/FtpClient.java
index 9526d3b..0c117d4 100644
--- a/ojluni/src/main/java/sun/net/ftp/impl/FtpClient.java
+++ b/ojluni/src/main/java/sun/net/ftp/impl/FtpClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff --git a/ojluni/src/main/java/sun/nio/ch/DatagramDispatcher.java b/ojluni/src/main/java/sun/nio/ch/DatagramDispatcher.java
index 0c5ec0b..328e4ee 100644
--- a/ojluni/src/main/java/sun/nio/ch/DatagramDispatcher.java
+++ b/ojluni/src/main/java/sun/nio/ch/DatagramDispatcher.java
@@ -62,7 +62,7 @@
}
void preClose(FileDescriptor fd) throws IOException {
- FileDispatcherImpl.preCloseImpl(fd);
+ FileDispatcherImpl.preClose0(fd);
}
static native int read0(FileDescriptor fd, long address, int len)
diff --git a/ojluni/src/main/java/sun/nio/ch/DefaultSelectorProvider.java b/ojluni/src/main/java/sun/nio/ch/DefaultSelectorProvider.java
index 75bfd13..bafb74e 100644
--- a/ojluni/src/main/java/sun/nio/ch/DefaultSelectorProvider.java
+++ b/ojluni/src/main/java/sun/nio/ch/DefaultSelectorProvider.java
@@ -128,11 +128,6 @@
overhead to set up when the number of FDs being polled is small
(which is the common case on Android).
- We also need to make sure that all tagged sockets are untagged before
- they're preclosed at the platform level. However, there's nothing we
- can do about applications that abuse public api (android.net.TrafficStats).
-
-
ALTERNATE APPROACHES :
----------------------
For completeness, I'm listing a couple of other approaches that were
diff --git a/ojluni/src/main/java/sun/nio/ch/FileDispatcherImpl.java b/ojluni/src/main/java/sun/nio/ch/FileDispatcherImpl.java
index 96163c2..b530b3a 100644
--- a/ojluni/src/main/java/sun/nio/ch/FileDispatcherImpl.java
+++ b/ojluni/src/main/java/sun/nio/ch/FileDispatcherImpl.java
@@ -26,10 +26,8 @@
package sun.nio.ch;
import dalvik.system.BlockGuard;
-import dalvik.system.SocketTagger;
import java.io.*;
-import java.net.SocketException;
class FileDispatcherImpl extends FileDispatcher {
@@ -108,19 +106,6 @@
}
void preClose(FileDescriptor fd) throws IOException {
- preCloseImpl(fd);
- }
-
- static void preCloseImpl(FileDescriptor fd) throws IOException {
- if (fd.isSocket$()) {
- // Always untag sockets before the preClose. The file descriptor will describe
- // a different file (/dev/null) after preClose0.
- try {
- SocketTagger.get().untag(fd);
- } catch (SocketException ignored) {
- }
- }
-
preClose0(fd);
}
@@ -174,7 +159,7 @@
static native void close0(FileDescriptor fd) throws IOException;
- private static native void preClose0(FileDescriptor fd) throws IOException;
+ static native void preClose0(FileDescriptor fd) throws IOException;
static native void closeIntFD(int fd) throws IOException;
diff --git a/ojluni/src/main/java/sun/nio/ch/SocketDispatcher.java b/ojluni/src/main/java/sun/nio/ch/SocketDispatcher.java
index 4f4e469..8dde1f9 100644
--- a/ojluni/src/main/java/sun/nio/ch/SocketDispatcher.java
+++ b/ojluni/src/main/java/sun/nio/ch/SocketDispatcher.java
@@ -26,7 +26,6 @@
package sun.nio.ch;
import dalvik.system.BlockGuard;
-import dalvik.system.SocketTagger;
import java.io.*;
@@ -63,6 +62,6 @@
}
void preClose(FileDescriptor fd) throws IOException {
- FileDispatcherImpl.preCloseImpl(fd);
+ FileDispatcherImpl.preClose0(fd);
}
}
diff --git a/ojluni/src/main/java/sun/security/jca/Providers.java b/ojluni/src/main/java/sun/security/jca/Providers.java
index e50ce23..b0026b3 100644
--- a/ojluni/src/main/java/sun/security/jca/Providers.java
+++ b/ojluni/src/main/java/sun/security/jca/Providers.java
@@ -348,7 +348,8 @@
}
// The set of algorithms that are deprecated. This list is created using
- // libcore/tools/crypto/src/java/libcore/java/security/ProviderOverlap.java
+ // libcore/tools/crypto/src/java/libcore/java/security/ProviderOverlap.java, with
+ // additional Ciphers added manually (see comment below).
private static final Set<String> DEPRECATED_ALGORITHMS = new HashSet<String>();
static {
DEPRECATED_ALGORITHMS.addAll(Arrays.asList(
@@ -367,16 +368,27 @@
"ALGORITHMPARAMETERS.TDEA",
"CERTIFICATEFACTORY.X.509",
"CERTIFICATEFACTORY.X509",
- // TODO(flooey, b/67626877): Implement Cipher support
- // "CIPHER.1.2.840.113549.3.4",
- // "CIPHER.2.16.840.1.101.3.4.1.26",
- // "CIPHER.2.16.840.1.101.3.4.1.46",
- // "CIPHER.2.16.840.1.101.3.4.1.6",
- // "CIPHER.AES/GCM/NOPADDING",
- // "CIPHER.ARC4",
- // "CIPHER.ARCFOUR",
- // "CIPHER.OID.1.2.840.113549.3.4",
- // "CIPHER.RC4",
+ // List of Ciphers produced by ProviderOverlap:
+ "CIPHER.1.2.840.113549.3.4",
+ "CIPHER.2.16.840.1.101.3.4.1.26",
+ "CIPHER.2.16.840.1.101.3.4.1.46",
+ "CIPHER.2.16.840.1.101.3.4.1.6",
+ "CIPHER.AES/GCM/NOPADDING",
+ "CIPHER.ARC4",
+ "CIPHER.ARCFOUR",
+ "CIPHER.OID.1.2.840.113549.3.4",
+ "CIPHER.RC4",
+ // End of Ciphers produced by ProviderOverlap
+ // Additional ciphers transformations that will resolve to the same things as
+ // the automatically-produced overlap due to the Cipher transformation rules.
+ // These have been added manually.
+ "CIPHER.ARC4/ECB/NOPADDING",
+ "CIPHER.ARC4/NONE/NOPADDING",
+ "CIPHER.ARCFOUR/ECB/NOPADDING",
+ "CIPHER.ARCFOUR/NONE/NOPADDING",
+ "CIPHER.RC4/ECB/NOPADDING",
+ "CIPHER.RC4/NONE/NOPADDING",
+ // End of additional Ciphers
"KEYAGREEMENT.ECDH",
"KEYFACTORY.1.2.840.10045.2.1",
"KEYFACTORY.1.2.840.113549.1.1.1",
diff --git a/ojluni/src/main/native/Net.c b/ojluni/src/main/native/Net.c
index 01ea3f2..550711e 100644
--- a/ojluni/src/main/native/Net.c
+++ b/ojluni/src/main/native/Net.c
@@ -255,7 +255,6 @@
JNU_ThrowByNameWithLastError(env,
JNU_JAVANETPKG "SocketException",
"Unable to set IPV6_V6ONLY");
- untagSocket(env, fd);
close(fd);
return -1;
}
@@ -269,7 +268,6 @@
JNU_ThrowByNameWithLastError(env,
JNU_JAVANETPKG "SocketException",
"Unable to set SO_REUSEADDR");
- untagSocket(env, fd);
close(fd);
return -1;
}
@@ -299,7 +297,6 @@
JNU_ThrowByNameWithLastError(env,
JNU_JAVANETPKG "SocketException",
"Unable to set IPV6_MULTICAST_HOPS");
- untagSocket(env, fd);
close(fd);
return -1;
}
diff --git a/ojluni/src/main/native/net_util_md.c b/ojluni/src/main/native/net_util_md.c
index d15e29d..3fb3eb6 100644
--- a/ojluni/src/main/native/net_util_md.c
+++ b/ojluni/src/main/native/net_util_md.c
@@ -398,7 +398,6 @@
/**
* SIOCGLIFNUM failed - assume IPv6 not configured
*/
- untagSocket(env, fd);
close(fd);
return JNI_FALSE;
}
diff --git a/ojluni/src/main/native/net_util_md.h b/ojluni/src/main/native/net_util_md.h
index 5c6dd42..94a8877 100644
--- a/ojluni/src/main/native/net_util_md.h
+++ b/ojluni/src/main/native/net_util_md.h
@@ -60,7 +60,6 @@
#endif
extern int tagSocket(JNIEnv* env, int fd);
-extern void untagSocket(JNIEnv* env, int fd);
#else
@@ -79,7 +78,6 @@
#define NET_Poll poll
#define tagSocket(env,fd) (void)0
-#define untagSocket(env,fd) (void)0
#endif
diff --git a/ojluni/src/main/native/socket_tagger_util.cpp b/ojluni/src/main/native/socket_tagger_util.cpp
index 611b92e..b62898f 100644
--- a/ojluni/src/main/native/socket_tagger_util.cpp
+++ b/ojluni/src/main/native/socket_tagger_util.cpp
@@ -37,17 +37,4 @@
return fd;
}
-void untagSocket(JNIEnv* env, int fd) {
- if (env->ExceptionOccurred()) { return; }
- jmethodID get = env->GetStaticMethodID(JniConstants::socketTaggerClass,
- "get", "()Ldalvik/system/SocketTagger;");
- jobject socketTagger =
- env->CallStaticObjectMethod(JniConstants::socketTaggerClass, get);
- jmethodID untag = env->GetMethodID(JniConstants::socketTaggerClass,
- "untag", "(Ljava/io/FileDescriptor;)V");
-
- jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
- env->CallVoidMethod(socketTagger, untag, fileDescriptor);
-}
-
}