Merge "Missing 1.8 javax.net.ssl.SSLSocketFactory.createSocket method"
diff --git a/AndroidTest.xml b/AndroidTest.xml
new file mode 100644
index 0000000..78ed943
--- /dev/null
+++ b/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Config for libjavacore-benchmarks">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="libjavacore-benchmarks->/data/benchmarktest/libjavacore-benchmarks" />
+ </target_preparer>
+ <option name="test-suite-tag" value="apct" />
+ <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
+ <option name="native-benchmark-device-path" value="/data/benchmarktest" />
+ <option name="benchmark-module-name" value="libjavacore-benchmarks" />
+ </test>
+</configuration>
diff --git a/JavaLibrary.mk b/JavaLibrary.mk
index 758b6ce..97e94f8 100644
--- a/JavaLibrary.mk
+++ b/JavaLibrary.mk
@@ -91,7 +91,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LANGUAGE_VERSION := 1.8
LOCAL_MODULE := core-all
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
LOCAL_CORE_LIBRARY := true
LOCAL_UNINSTALLABLE_MODULE := true
include $(BUILD_JAVA_LIBRARY)
@@ -110,7 +110,7 @@
LOCAL_MODULE := core-oj
LOCAL_JAVA_LIBRARIES := core-all
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/ojluni/NOTICE
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
LOCAL_CORE_LIBRARY := true
include $(BUILD_JAVA_LIBRARY)
@@ -132,7 +132,7 @@
endif # EMMA_INSTRUMENT_STATIC
endif # EMMA_INSTRUMENT
LOCAL_CORE_LIBRARY := true
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
include $(BUILD_JAVA_LIBRARY)
# A library that exists to satisfy javac when
@@ -167,7 +167,7 @@
LOCAL_MODULE := core-oj-testdex
LOCAL_JAVA_LIBRARIES := core-all
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/ojluni/NOTICE
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
LOCAL_CORE_LIBRARY := true
include $(BUILD_JAVA_LIBRARY)
@@ -201,7 +201,7 @@
LOCAL_MODULE := core-libart-testdex
LOCAL_JAVA_LIBRARIES := core-all
LOCAL_CORE_LIBRARY := true
-LOCAL_REQUIRED_MODULES := tzdata
+LOCAL_REQUIRED_MODULES := tzdata tzlookup.xml
include $(BUILD_JAVA_LIBRARY)
endif
@@ -330,7 +330,7 @@
LOCAL_MODULE_TAGS := optional
LOCAL_JAVA_LANGUAGE_VERSION := 1.8
LOCAL_MODULE := core-all-hostdex
-LOCAL_REQUIRED_MODULES := tzdata-host
+LOCAL_REQUIRED_MODULES := tzdata-host tzlookup.xml-host
LOCAL_CORE_LIBRARY := true
LOCAL_UNINSTALLABLE_MODULE := true
include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
@@ -346,7 +346,7 @@
LOCAL_MODULE := core-oj-hostdex
LOCAL_NOTICE_FILE := $(LOCAL_PATH)/ojluni/NOTICE
LOCAL_JAVA_LIBRARIES := core-all-hostdex
-LOCAL_REQUIRED_MODULES := tzdata-host
+LOCAL_REQUIRED_MODULES := tzdata-host tzlookup.xml-host
LOCAL_CORE_LIBRARY := true
include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
@@ -361,7 +361,7 @@
LOCAL_JAVA_LANGUAGE_VERSION := 1.8
LOCAL_MODULE := core-libart-hostdex
LOCAL_JAVA_LIBRARIES := core-oj-hostdex
-LOCAL_REQUIRED_MODULES := tzdata-host
+LOCAL_REQUIRED_MODULES := tzdata-host tzlookup.xml-host
include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
# A library that exists to satisfy javac when
diff --git a/NativeCode.mk b/NativeCode.mk
index d8e9d64..9bfb88d 100644
--- a/NativeCode.mk
+++ b/NativeCode.mk
@@ -204,6 +204,7 @@
LOCAL_C_INCLUDES += libcore/include
LOCAL_MODULE_TAGS := debug
LOCAL_MODULE := libjavacore-benchmarks
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/NativeCode.mk
LOCAL_SHARED_LIBRARIES := libnativehelper
LOCAL_CXX_STL := libc++
diff --git a/benchmarks/src/benchmarks/regression/StringReplaceAllBenchmark.java b/benchmarks/src/benchmarks/regression/StringReplaceAllBenchmark.java
new file mode 100644
index 0000000..73e7979
--- /dev/null
+++ b/benchmarks/src/benchmarks/regression/StringReplaceAllBenchmark.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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 benchmarks.regression;
+
+import com.google.caliper.Param;
+
+public class StringReplaceAllBenchmark {
+ // NOTE: These estimates of MOVEABLE / NON_MOVEABLE are based on a knowledge of
+ // ART implementation details. They make a difference here because JNI calls related
+ // to strings took different paths depending on whether the String in question was
+ // moveable or not.
+ enum StringLengths {
+ EMPTY(""),
+ MOVEABLE_16(makeString(16)),
+ MOVEABLE_256(makeString(256)),
+ MOVEABLE_1024(makeString(1024)),
+ NON_MOVEABLE(makeString(64 * 1024)),
+ BOOT_IMAGE(java.util.jar.JarFile.MANIFEST_NAME);
+
+ private final String value;
+
+ StringLengths(String s) {
+ this.value = s;
+ }
+ }
+
+ private static final String makeString(int length) {
+ final String sequence8 = "abcdefghijklmnop";
+ final int numAppends = (length / 16) - 1;
+ StringBuilder stringBuilder = new StringBuilder(length);
+
+ // (n-1) occurences of "abcdefghijklmnop"
+ for (int i = 0; i < numAppends; ++i) {
+ stringBuilder.append(sequence8);
+ }
+
+ // and one final occurence of qrstuvwx.
+ stringBuilder.append("qrstuvwx");
+
+ return stringBuilder.toString();
+ }
+
+ @Param private StringLengths s;
+
+ public void timeReplaceAllTrivialPatternNonExistent(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ s.value.replaceAll("fish", "0");
+ }
+ }
+
+ public void timeReplaceTrivialPatternAllRepeated(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ s.value.replaceAll("jklm", "0");
+ }
+ }
+
+ public void timeReplaceAllTrivialPatternSingleOccurence(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ s.value.replaceAll("qrst", "0");
+ }
+ }
+}
diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java
index b9e607a..3693bb2 100644
--- a/dalvik/src/main/java/dalvik/system/DexPathList.java
+++ b/dalvik/src/main/java/dalvik/system/DexPathList.java
@@ -441,8 +441,6 @@
} else if (file.isDirectory()) {
// We support directories for looking up native libraries.
elements[elementsPos++] = new NativeLibraryElement(file);
- } else {
- System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
diff --git a/dalvik/src/main/java/dalvik/system/ZygoteHooks.java b/dalvik/src/main/java/dalvik/system/ZygoteHooks.java
index b29b895..c96016f 100644
--- a/dalvik/src/main/java/dalvik/system/ZygoteHooks.java
+++ b/dalvik/src/main/java/dalvik/system/ZygoteHooks.java
@@ -69,7 +69,7 @@
* {@code postForkChild}.
*/
public void postForkCommon() {
- Daemons.start();
+ Daemons.startPostZygoteFork();
}
private static native long nativePreFork();
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index 1f0ef32..58a4337 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -1505,11 +1505,37 @@
]
},
{
- description: "Waiting for ICU 58 to be merged",
- bug: 31516121,
+ description: "Bouncy Castle doesn't prevent reusing initialization vectors",
+ bug: 31801320,
result: EXEC_FAILED,
names: [
- "libcore.java.text.OldBidiTest#testUnicode9EmojisAreLtrNeutral"
+ "com.google.security.wycheproof.AesGcmTest#testIvReuse"
+ ]
+},
+{
+ description: "Conscrypt produces different exceptions under different circumstances",
+ bug: 36772622,
+ result: EXEC_FAILED,
+ names: [
+ "com.google.security.wycheproof.RsaEncryptionTest#testExceptionsPKCS1",
+ "com.google.security.wycheproof.RsaEncryptionTest#testGetExceptionsOAEP"
+ ]
+},
+{
+ description: "Conscrypt fails these tests in 32-bit mode",
+ bug: 36636626,
+ result: EXEC_FAILED,
+ names: [
+ "com.google.security.wycheproof.EcdhTest#testModifiedPublic",
+ "com.google.security.wycheproof.EcdhTest#testModifiedPublicSpec"
+ ]
+},
+{
+ description: "Bullhead kernel does not block send when buffer is supposed to have saturated",
+ bug: 36691333,
+ result: EXEC_FAILED,
+ names: [
+ "libcore.java.net.SocketTimeoutTest#testSocketWriteNeverTimeouts"
]
}
]
diff --git a/libart/src/main/java/dalvik/system/ClassExt.java b/libart/src/main/java/dalvik/system/ClassExt.java
index 772bd2e..9c9fa7a 100644
--- a/libart/src/main/java/dalvik/system/ClassExt.java
+++ b/libart/src/main/java/dalvik/system/ClassExt.java
@@ -48,7 +48,8 @@
private Object obsoleteMethods;
/**
- * If set, the bytes of the original dex-file associated with the related class.
+ * If set, the bytes, native pointer (as a java.lang.Long), or DexCache of the original dex-file
+ * associated with the related class.
*
* In this instance 'original' means either (1) the dex-file loaded for this class when it was
* first loaded after all non-retransformation capable transformations had been performed but
@@ -59,7 +60,7 @@
*
* This field is a logical part of the 'Class' type.
*/
- private byte[] originalDexFile;
+ private Object originalDexFile;
/**
* If class verify fails, we must return same error on subsequent tries. We may store either
diff --git a/libart/src/main/java/dalvik/system/VMRuntime.java b/libart/src/main/java/dalvik/system/VMRuntime.java
index 6a673f2..d553846 100644
--- a/libart/src/main/java/dalvik/system/VMRuntime.java
+++ b/libart/src/main/java/dalvik/system/VMRuntime.java
@@ -421,4 +421,9 @@
* Should be called just once. Subsequent calls are ignored.
*/
public static native void registerSensitiveThread();
+
+ /**
+ * Sets up the priority of the system daemon thread (caller).
+ */
+ public static native void setSystemDaemonThreadPriority();
}
diff --git a/libart/src/main/java/java/lang/Daemons.java b/libart/src/main/java/java/lang/Daemons.java
index 15a8ab6..0e2cb45 100644
--- a/libart/src/main/java/java/lang/Daemons.java
+++ b/libart/src/main/java/java/lang/Daemons.java
@@ -46,6 +46,13 @@
HeapTaskDaemon.INSTANCE.start();
}
+ public static void startPostZygoteFork() {
+ ReferenceQueueDaemon.INSTANCE.startPostZygoteFork();
+ FinalizerDaemon.INSTANCE.startPostZygoteFork();
+ FinalizerWatchdogDaemon.INSTANCE.startPostZygoteFork();
+ HeapTaskDaemon.INSTANCE.startPostZygoteFork();
+ }
+
public static void stop() {
HeapTaskDaemon.INSTANCE.stop();
ReferenceQueueDaemon.INSTANCE.stop();
@@ -61,12 +68,22 @@
private static abstract class Daemon implements Runnable {
private Thread thread;
private String name;
+ private boolean postZygoteFork;
protected Daemon(String name) {
this.name = name;
}
public synchronized void start() {
+ startInternal();
+ }
+
+ public synchronized void startPostZygoteFork() {
+ postZygoteFork = true;
+ startInternal();
+ }
+
+ public void startInternal() {
if (thread != null) {
throw new IllegalStateException("already running");
}
@@ -75,7 +92,18 @@
thread.start();
}
- public abstract void run();
+ public void run() {
+ if (postZygoteFork) {
+ // We don't set the priority before the Thread.start() call above because
+ // Thread.start() will call SetNativePriority and overwrite the desired native
+ // priority. We (may) use a native priority that doesn't have a corresponding
+ // java.lang.Thread-level priority (native priorities are more coarse-grained.)
+ VMRuntime.getRuntime().setSystemDaemonThreadPriority();
+ }
+ runInternal();
+ }
+
+ public abstract void runInternal();
/**
* Returns true while the current thread should continue to run; false
@@ -141,7 +169,7 @@
super("ReferenceQueueDaemon");
}
- @Override public void run() {
+ @Override public void runInternal() {
while (isRunning()) {
Reference<?> list;
try {
@@ -173,7 +201,7 @@
super("FinalizerDaemon");
}
- @Override public void run() {
+ @Override public void runInternal() {
// This loop may be performance critical, since we need to keep up with mutator
// generation of finalizable objects.
// We minimize the amount of work we do per finalizable object. For example, we avoid
@@ -244,7 +272,7 @@
super("FinalizerWatchdogDaemon");
}
- @Override public void run() {
+ @Override public void runInternal() {
while (isRunning()) {
if (!sleepUntilNeeded()) {
// We have been interrupted, need to see if this daemon has been stopped.
@@ -419,7 +447,7 @@
VMRuntime.getRuntime().stopHeapTaskProcessor();
}
- @Override public void run() {
+ @Override public void runInternal() {
synchronized (this) {
if (isRunning()) {
// Needs to be synchronized or else we there is a race condition where we start
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index 0c34d5e..7adae8b 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -34,6 +34,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
+import java.net.NoRouteToHostException;
import java.net.PortUnreachableException;
import java.net.SocketAddress;
import java.net.SocketException;
@@ -128,6 +129,12 @@
try {
connectErrno(fd, inetAddress, port, timeoutMs);
} catch (ErrnoException errnoException) {
+ if (errnoException.errno == EHOSTUNREACH) {
+ throw new NoRouteToHostException("Host unreachable");
+ }
+ if (errnoException.errno == EADDRNOTAVAIL) {
+ throw new NoRouteToHostException("Address not available");
+ }
throw new ConnectException(connectDetail(fd, inetAddress, port, timeoutMs,
errnoException), errnoException);
} catch (SocketException ex) {
@@ -141,7 +148,7 @@
private static void connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws ErrnoException, IOException {
// With no timeout, just call connect(2) directly.
- if (timeoutMs == 0) {
+ if (timeoutMs <= 0) {
Libcore.os.connect(fd, inetAddress, port);
return;
}
diff --git a/luni/src/main/java/libcore/util/TimeZoneDataFiles.java b/luni/src/main/java/libcore/util/TimeZoneDataFiles.java
new file mode 100644
index 0000000..c792665
--- /dev/null
+++ b/luni/src/main/java/libcore/util/TimeZoneDataFiles.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+/**
+ * Utility methods associated with finding updateable time zone data files.
+ */
+public final class TimeZoneDataFiles {
+
+ private static final String ANDROID_ROOT_ENV = "ANDROID_ROOT";
+ private static final String ANDROID_DATA_ENV = "ANDROID_DATA";
+
+ private TimeZoneDataFiles() {}
+
+ /**
+ * Returns two time zone file paths for the specified file name in an array in the order they
+ * should be tried. See {@link #generateIcuDataPath()} for ICU files instead.
+ * <ul>
+ * <li>[0] - the location of the file in the /data partition (may not exist).</li>
+ * <li>[1] - the location of the file in the /system partition (should exist).</li>
+ * </ul>
+ */
+ // VisibleForTesting
+ public static String[] getTimeZoneFilePaths(String fileName) {
+ return new String[] {
+ getDataTimeZoneFile(fileName),
+ getSystemTimeZoneFile(fileName)
+ };
+ }
+
+ private static String getDataTimeZoneFile(String fileName) {
+ return System.getenv(ANDROID_DATA_ENV) + "/misc/zoneinfo/current/" + fileName;
+ }
+
+ // VisibleForTesting
+ public static String getSystemTimeZoneFile(String fileName) {
+ return System.getenv(ANDROID_ROOT_ENV) + "/usr/share/zoneinfo/" + fileName;
+ }
+
+ public static String generateIcuDataPath() {
+ StringBuilder icuDataPathBuilder = new StringBuilder();
+ // ICU should first look in ANDROID_DATA. This is used for (optional) timezone data.
+ String dataIcuDataPath = getEnvironmentPath(ANDROID_DATA_ENV, "/misc/zoneinfo/current/icu");
+ if (dataIcuDataPath != null) {
+ icuDataPathBuilder.append(dataIcuDataPath);
+ }
+
+ // ICU should always look in ANDROID_ROOT.
+ String systemIcuDataPath = getEnvironmentPath(ANDROID_ROOT_ENV, "/usr/icu");
+ if (systemIcuDataPath != null) {
+ if (icuDataPathBuilder.length() > 0) {
+ icuDataPathBuilder.append(":");
+ }
+ icuDataPathBuilder.append(systemIcuDataPath);
+ }
+ return icuDataPathBuilder.toString();
+ }
+
+ /**
+ * Creates a path by combining the value of an environment variable with a relative path.
+ * Returns {@code null} if the environment variable is not set.
+ */
+ private static String getEnvironmentPath(String environmentVariable, String path) {
+ String variable = System.getenv(environmentVariable);
+ if (variable == null) {
+ return null;
+ }
+ return variable + path;
+ }
+}
diff --git a/luni/src/main/java/libcore/util/TimeZoneFinder.java b/luni/src/main/java/libcore/util/TimeZoneFinder.java
new file mode 100644
index 0000000..4e47df4
--- /dev/null
+++ b/luni/src/main/java/libcore/util/TimeZoneFinder.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import android.icu.util.TimeZone;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A structure that can find matching time zones.
+ */
+public class TimeZoneFinder {
+
+ private static final String TZLOOKUP_FILE_NAME = "tzlookup.xml";
+ private static final String TIMEZONES_ELEMENT = "timezones";
+ 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 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;
+
+ private TimeZoneFinder(ReaderSupplier xmlSource) {
+ this.xmlSource = xmlSource;
+ }
+
+ /**
+ * Obtains an instance for use when resolving time zones. This method handles using the correct
+ * file when there are several to choose from. This method never returns {@code null}. No
+ * in-depth validation is performed on the file content, see {@link #validate()}.
+ */
+ public static TimeZoneFinder getInstance() {
+ synchronized(TimeZoneFinder.class) {
+ if (instance == null) {
+ String[] tzLookupFilePaths =
+ TimeZoneDataFiles.getTimeZoneFilePaths(TZLOOKUP_FILE_NAME);
+ instance = createInstanceWithFallback(tzLookupFilePaths[0], tzLookupFilePaths[1]);
+ }
+ }
+ return instance;
+ }
+
+ // VisibleForTesting
+ public static TimeZoneFinder createInstanceWithFallback(String... tzLookupFilePaths) {
+ for (String tzLookupFilePath : tzLookupFilePaths) {
+ try {
+ // We assume that any file in /data was validated before install, and the system
+ // file was validated before the device shipped. Therefore, we do not pay the
+ // validation cost here.
+ return createInstance(tzLookupFilePath);
+ } catch (IOException e) {
+ System.logE("Unable to process file: " + tzLookupFilePath + " Trying next one.", e);
+ }
+ }
+
+ System.logE("No valid file found in set: " + Arrays.toString(tzLookupFilePaths)
+ + " Falling back to empty map.");
+ return createInstanceForTests("<timezones><countryzones /></timezones>");
+ }
+
+ /**
+ * Obtains an instance using a specific data file, throwing an IOException if the file does not
+ * exist or is not readable. This method never returns {@code null}. No in-depth validation is
+ * performed on the file content, see {@link #validate()}.
+ */
+ public static TimeZoneFinder createInstance(String path) throws IOException {
+ ReaderSupplier xmlSupplier = ReaderSupplier.forFile(path, StandardCharsets.UTF_8);
+ return new TimeZoneFinder(xmlSupplier);
+ }
+
+ /** Used to create an instance using an in-memory XML String instead of a file. */
+ // VisibleForTesting
+ public static TimeZoneFinder createInstanceForTests(String xml) {
+ return new TimeZoneFinder(ReaderSupplier.forString(xml));
+ }
+
+ /**
+ * Parses the data file, throws an exception if it is invalid or cannot be read.
+ */
+ public void validate() throws IOException {
+ try {
+ processXml(new CountryZonesValidator());
+ } catch (XmlPullParserException e) {
+ throw new IOException("Parsing error", e);
+ }
+ }
+
+ /**
+ * Return a time zone that has / would have had the specified offset and DST value at the
+ * specified moment in the specified country.
+ *
+ * <p>In order to be considered a configured zone must match the supplied offset information.
+ *
+ * <p>Matches are considered in a well-defined order. If multiple zones match and one of them
+ * also matches the (optional) bias parameter then the bias time zone will be returned.
+ * Otherwise the first match found is returned.
+ */
+ public TimeZone lookupTimeZoneByCountryAndOffset(
+ String countryIso, int offsetSeconds, boolean isDst, long whenMillis, TimeZone bias) {
+
+ 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);
+ if (!offsetMatchesAtTime(match, offsetSeconds, isDst, whenMillis)) {
+ continue;
+ }
+
+ if (firstMatch == null) {
+ if (bias == null) {
+ // No bias, so we can stop at the first match.
+ return match;
+ }
+ // We have to carry on checking in case the bias matches. We want to return the
+ // first if it doesn't, though.
+ firstMatch = match;
+ }
+
+ // Check if match is also the bias. There must be a bias otherwise we'd have terminated
+ // already.
+ if (match.getID().equals(bias.getID())) {
+ return match;
+ }
+ }
+ // Return firstMatch, which can be null if there was no match.
+ return firstMatch;
+ }
+
+ /**
+ * Returns {@code true} if the specified offset, DST state and time would be valid in the
+ * timeZone.
+ */
+ private static boolean offsetMatchesAtTime(TimeZone timeZone, int offsetMillis, boolean isDst,
+ long whenMillis) {
+ int[] offsets = new int[2];
+ timeZone.getOffset(whenMillis, false /* local */, offsets);
+
+ // offsets[1] == 0 when the zone is not in DST.
+ boolean zoneIsDst = offsets[1] != 0;
+ if (isDst != zoneIsDst) {
+ return false;
+ }
+ return offsetMillis == (offsets[0] + offsets[1]);
+ }
+
+ /**
+ * Returns a list of 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 zone IDs.
+ */
+ public List<TimeZone> lookupTimeZonesByCountry(String countryIso) {
+ synchronized(this) {
+ if (countryIso.equals(lastCountryIso)) {
+ return lastCountryTimeZones;
+ }
+ }
+
+ CountryZonesExtractor extractor = new CountryZonesExtractor(countryIso);
+ List<TimeZone> countryTimeZones = null;
+ try {
+ processXml(extractor);
+ countryTimeZones = extractor.getMatchedZones();
+ } catch (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.
+ }
+
+ 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#HALT} or it throws an exception.
+ */
+ private void processXml(CountryZonesProcessor processor)
+ throws XmlPullParserException, IOException {
+ 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>
+ * <countryzones>
+ * <country code="us">
+ * <id>America/New_York"</id>
+ * ...
+ * <id>America/Los_Angeles</id>
+ * </country>
+ * <country code="gb">
+ * <id>Europe/London</id>
+ * </country>
+ * </countryzones>
+ * </timezones>
+ */
+
+ findRequiredStartTag(parser, TIMEZONES_ELEMENT);
+
+ // There is only one expected sub-element <countryzones> in the format currently, skip
+ // over anything before it.
+ findRequiredStartTag(parser, COUNTRY_ZONES_ELEMENT);
+
+ if (processCountryZones(parser, processor) == CountryZonesProcessor.HALT) {
+ return;
+ }
+
+ // Make sure we are on the </countryzones> tag.
+ checkOnEndTag(parser, COUNTRY_ZONES_ELEMENT);
+
+ // Advance to the next tag.
+ parser.next();
+
+ // Skip anything until </timezones>, and make sure the file is not truncated and we can
+ // find the end.
+ consumeUntilEndTag(parser, TIMEZONES_ELEMENT);
+
+ // Make sure we are on the </timezones> tag.
+ checkOnEndTag(parser, TIMEZONES_ELEMENT);
+ }
+ }
+
+ private static boolean processCountryZones(XmlPullParser parser,
+ CountryZonesProcessor processor) throws IOException, XmlPullParserException {
+
+ // Skip over any unexpected elements and process <country> elements.
+ while (findOptionalStartTag(parser, COUNTRY_ELEMENT)) {
+ if (processor == null) {
+ consumeUntilEndTag(parser, COUNTRY_ELEMENT);
+ } else {
+ String code = parser.getAttributeValue(
+ null /* namespace */, COUNTRY_CODE_ATTRIBUTE);
+ if (code == null || code.isEmpty()) {
+ throw new XmlPullParserException(
+ "Unable to find country code: " + parser.getPositionDescription());
+ }
+
+ String debugInfo = parser.getPositionDescription();
+ List<String> timeZoneIds = parseZoneIds(parser);
+ if (processor.process(code, timeZoneIds, debugInfo)
+ == CountryZonesProcessor.HALT) {
+ return CountryZonesProcessor.HALT;
+ }
+ }
+
+ // Make sure we are on the </country> element.
+ checkOnEndTag(parser, COUNTRY_ELEMENT);
+ }
+
+ return CountryZonesExtractor.CONTINUE;
+ }
+
+ private static List<String> parseZoneIds(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ List<String> timeZones = new ArrayList<>();
+
+ // Skip over any unexpected elements and process <id> elements.
+ while (findOptionalStartTag(parser, ID_ELEMENT)) {
+ String zoneIdString = consumeText(parser);
+
+ // Make sure we are on the </id> element.
+ checkOnEndTag(parser, ID_ELEMENT);
+
+ // Process the zone ID.
+ timeZones.add(zoneIdString);
+ }
+
+ // The list is made unmodifiable to avoid callers changing it.
+ return Collections.unmodifiableList(timeZones);
+ }
+
+ private static void findRequiredStartTag(XmlPullParser parser, String elementName)
+ throws IOException, XmlPullParserException {
+ findStartTag(parser, elementName, true /* elementRequired */);
+ }
+
+ /** Called when on a START_TAG. When returning false, it leaves the parser on the END_TAG. */
+ private static boolean findOptionalStartTag(XmlPullParser parser, String elementName)
+ throws IOException, XmlPullParserException {
+ return findStartTag(parser, elementName, false /* elementRequired */);
+ }
+
+ /**
+ * Find a START_TAG with the specified name without decreasing the depth, or increasing the
+ * depth by more than one. More deeply nested elements and text are skipped, even START_TAGs
+ * with matching names. Returns when the START_TAG is found or the next (non-nested) END_TAG is
+ * encountered. The return can take the form of an exception or a false if the START_TAG is not
+ * found. True is returned when it is.
+ */
+ private static boolean findStartTag(
+ XmlPullParser parser, String elementName, boolean elementRequired)
+ throws IOException, XmlPullParserException {
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ switch (type) {
+ case XmlPullParser.START_TAG:
+ String currentElementName = parser.getName();
+ if (elementName.equals(currentElementName)) {
+ return true;
+ }
+
+ // It was not the START_TAG we were looking for. Consume until the end.
+ parser.next();
+ consumeUntilEndTag(parser, currentElementName);
+ break;
+ case XmlPullParser.END_TAG:
+ if (elementRequired) {
+ throw new XmlPullParserException(
+ "No child element found with name " + elementName);
+ }
+ return false;
+ default:
+ // Ignore.
+ break;
+ }
+ }
+ throw new XmlPullParserException("Unexpected end of document while looking for "
+ + elementName);
+ }
+
+ /**
+ * Consume the remaining contents of an element and move to the END_TAG. Used when processing
+ * within an element can stop. The parser must be pointing at either the END_TAG we are looking
+ * for, a TEXT, or a START_TAG nested within the element to be consumed.
+ */
+ private static void consumeUntilEndTag(XmlPullParser parser, String elementName)
+ throws IOException, XmlPullParserException {
+
+ if (parser.getEventType() == XmlPullParser.END_TAG
+ && elementName.equals(parser.getName())) {
+ // Early return - we are already there.
+ return;
+ }
+
+ // Keep track of the required depth in case there are nested elements to be consumed.
+ // Both the name and the depth must match our expectation to complete.
+
+ int requiredDepth = parser.getDepth();
+ // A TEXT tag would be at the same depth as the END_TAG we are looking for.
+ if (parser.getEventType() == XmlPullParser.START_TAG) {
+ // A START_TAG would have incremented the depth, so we're looking for an END_TAG one
+ // higher than the current tag.
+ requiredDepth--;
+ }
+
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ int type = parser.next();
+
+ int currentDepth = parser.getDepth();
+ if (currentDepth < requiredDepth) {
+ throw new XmlPullParserException(
+ "Unexpected depth while looking for end tag: "
+ + parser.getPositionDescription());
+ } else if (currentDepth == requiredDepth) {
+ if (type == XmlPullParser.END_TAG) {
+ if (elementName.equals(parser.getName())) {
+ return;
+ }
+ throw new XmlPullParserException(
+ "Unexpected eng tag: " + parser.getPositionDescription());
+ }
+ }
+ // Everything else is either a type we are not interested in or is too deep and so is
+ // ignored.
+ }
+ throw new XmlPullParserException("Unexpected end of document");
+ }
+
+ /**
+ * Reads the text inside the current element. Should be called when the parser is currently
+ * on the START_TAG before the TEXT. The parser will be positioned on the END_TAG after this
+ * call when it completes successfully.
+ */
+ private static String consumeText(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ int type = parser.next();
+ String text;
+ if (type == XmlPullParser.TEXT) {
+ text = parser.getText();
+ } else {
+ throw new XmlPullParserException("Text not found. Found type=" + type
+ + " at " + parser.getPositionDescription());
+ }
+
+ type = parser.next();
+ if (type != XmlPullParser.END_TAG) {
+ throw new XmlPullParserException(
+ "Unexpected nested tag or end of document when expecting text: type=" + type
+ + " at " + parser.getPositionDescription());
+ }
+ return text;
+ }
+
+ private static void checkOnEndTag(XmlPullParser parser, String elementName)
+ throws XmlPullParserException {
+ if (!(parser.getEventType() == XmlPullParser.END_TAG
+ && parser.getName().equals(elementName))) {
+ throw new XmlPullParserException(
+ "Unexpected tag encountered: " + parser.getPositionDescription());
+ }
+ }
+
+ /**
+ * Processes <countryzones> data.
+ */
+ private interface CountryZonesProcessor {
+
+ boolean CONTINUE = true;
+ boolean HALT = false;
+
+ /**
+ * Returns {@code #CONTINUE} if processing of the XML should continue, {@code HALT} if it
+ * 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;
+ }
+
+ /**
+ * Validates <countryzones> elements. To be valid the country ISO code must be unique
+ * and it must not be empty.
+ */
+ 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
+ + " at " + debugInfo);
+ }
+ if (timeZoneIds.isEmpty()) {
+ throw new XmlPullParserException("No time zone IDs for country code: " + countryCode
+ + " 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);
+
+ return CONTINUE;
+ }
+ }
+
+ /**
+ * Extracts the zones associated with a country code, halting when the country code is matched
+ * and making them available via {@link #getMatchedZones()}.
+ */
+ private static class CountryZonesExtractor implements CountryZonesProcessor {
+
+ private final String countryCodeToMatch;
+ private List<TimeZone> matchedZones;
+
+ private CountryZonesExtractor(String countryCodeToMatch) {
+ this.countryCodeToMatch = countryCodeToMatch;
+ }
+
+ @Override
+ public boolean process(String countryCode, List<String> timeZoneIds, String debugInfo) {
+ if (!countryCodeToMatch.equals(countryCode)) {
+ return CONTINUE;
+ }
+
+ 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.
+ */
+ List<TimeZone> getMatchedZones() {
+ return matchedZones;
+ }
+ }
+
+ /**
+ * A source of Readers that can be used repeatedly.
+ */
+ private interface ReaderSupplier {
+ /** Returns a Reader. Throws an IOException if the Reader cannot be created. */
+ Reader get() throws IOException;
+
+ static ReaderSupplier forFile(String fileName, Charset charSet) throws IOException {
+ Path file = Paths.get(fileName);
+ if (!Files.exists(file)) {
+ throw new FileNotFoundException(fileName + " does not exist");
+ }
+ if (!Files.isRegularFile(file) && Files.isReadable(file)) {
+ throw new IOException(fileName + " must be a regular readable file.");
+ }
+ return () -> Files.newBufferedReader(file, charSet);
+ }
+
+ static ReaderSupplier forString(String xml) {
+ return () -> new StringReader(xml);
+ }
+ }
+}
diff --git a/luni/src/main/java/libcore/util/ZoneInfoDB.java b/luni/src/main/java/libcore/util/ZoneInfoDB.java
index d9ff323..acb9c12 100644
--- a/luni/src/main/java/libcore/util/ZoneInfoDB.java
+++ b/luni/src/main/java/libcore/util/ZoneInfoDB.java
@@ -37,9 +37,12 @@
* @hide - used to implement TimeZone
*/
public final class ZoneInfoDB {
- private static final TzData DATA = TzData.loadTzDataWithFallback(
- System.getenv("ANDROID_DATA") + "/misc/zoneinfo/current/tzdata",
- System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata");
+
+ // VisibleForTesting
+ public static final String TZDATA_FILE = "tzdata";
+
+ private static final TzData DATA =
+ TzData.loadTzDataWithFallback(TimeZoneDataFiles.getTimeZoneFilePaths(TZDATA_FILE));
public static class TzData {
@@ -114,7 +117,7 @@
// We didn't find any usable tzdata on disk, so let's just hard-code knowledge of "GMT".
// This is actually implemented in TimeZone itself, so if this is the only time zone
// we report, we won't be asked any more questions.
- System.logE("Couldn't find any tzdata!");
+ System.logE("Couldn't find any " + TZDATA_FILE + " file!");
return TzData.createFallback();
}
@@ -183,7 +186,7 @@
// Something's wrong with the file.
// Log the problem and return false so we try the next choice.
- System.logE("tzdata file \"" + path + "\" was present but invalid!", ex);
+ System.logE(TZDATA_FILE + " file \"" + path + "\" was present but invalid!", ex);
return false;
}
}
diff --git a/luni/src/main/native/java_util_regex_Matcher.cpp b/luni/src/main/native/java_util_regex_Matcher.cpp
index 2461afc..2271ba8 100644
--- a/luni/src/main/native/java_util_regex_Matcher.cpp
+++ b/luni/src/main/native/java_util_regex_Matcher.cpp
@@ -16,75 +16,114 @@
#define LOG_TAG "Matcher"
+#include <memory>
#include <stdlib.h>
+#include <android-base/logging.h>
+
#include "IcuUtilities.h"
#include "JNIHelp.h"
#include "JniConstants.h"
#include "JniException.h"
-#include "ScopedPrimitiveArray.h"
#include "ScopedJavaUnicodeString.h"
+#include "ScopedPrimitiveArray.h"
+#include "ScopedStringChars.h"
#include "jni.h"
#include "unicode/parseerr.h"
#include "unicode/regex.h"
// ICU documentation: http://icu-project.org/apiref/icu4c/classRegexMatcher.html
-static icu::RegexMatcher* toRegexMatcher(jlong address) {
- return reinterpret_cast<icu::RegexMatcher*>(static_cast<uintptr_t>(address));
-}
-
/**
- * We use ICU4C's RegexMatcher class, but our input is on the Java heap and potentially moving
- * around between calls. This wrapper class ensures that our RegexMatcher is always pointing at
- * the current location of the char[]. Earlier versions of Android simply copied the data to the
- * native heap, but that's wasteful and hides allocations from the garbage collector.
+ * Encapsulates an instance of ICU4C's RegexMatcher class along with a copy of
+ * the input it's currently operating on in the native heap.
+ *
+ * Rationale: We choose to make a copy here because it turns out to be a lot
+ * cheaper when a moving GC and/or string compression is enabled. This is
+ * because env->GetStringChars() always copies in this scenario. This becomes
+ * especially bad when the String in question is long and/or contains a large
+ * number of matches.
+ *
+ * Drawbacks: The native allocation associated with this class is no longer
+ * fixed size, so we're effectively lying to the NativeAllocationRegistry about
+ * the size of the object(s) we're allocating on the native heap. The peak
+ * memory usage doesn't change though, given that GetStringChars would have
+ * made an allocation of precisely the same size.
*/
-class MatcherAccessor {
+class MatcherState {
public:
- MatcherAccessor(JNIEnv* env, jlong address, jstring javaInput, bool reset) {
- init(env, address);
+ MatcherState(icu::RegexMatcher* matcher) :
+ mMatcher(matcher),
+ mUChars(nullptr),
+ mUText(nullptr),
+ mStatus(U_ZERO_ERROR) {
+ }
- mJavaInput = javaInput;
- mChars = env->GetStringChars(mJavaInput, NULL);
- if (mChars == NULL) {
- return;
+ bool updateInput(JNIEnv* env, jstring input) {
+ // First, close the UText struct, since we're about to allocate a new one.
+ if (mUText != nullptr) {
+ utext_close(mUText);
+ mUText = nullptr;
}
- mUText = utext_openUChars(NULL, mChars, env->GetStringLength(mJavaInput), &mStatus);
- if (mUText == NULL) {
- return;
+ // Then delete the UChar* associated with the UText struct..
+ mUChars.reset(nullptr);
+
+ // TODO: We should investigate whether we can avoid an additional copy
+ // in the native heap when is_copy == JNI_TRUE. The problem with doing
+ // that is that we might call ReleaseStringChars with a different
+ // JNIEnv* on a different downcall. This is currently safe as
+ // implemented in ART, but is unlikely to be portable and the spec stays
+ // silent on the matter.
+ ScopedStringChars inputChars(env, input);
+ if (inputChars.get() == nullptr) {
+ // There will be an exception pending if we get here.
+ return false;
}
- if (reset) {
- mMatcher->reset(mUText);
- } else {
- mMatcher->refreshInputText(mUText, mStatus);
+ // Make a copy of |input| on the native heap. This copy will be live
+ // until the next call to updateInput or close.
+ mUChars.reset(new (std::nothrow) UChar[inputChars.size()]);
+ if (mUChars.get() == nullptr) {
+ env->ThrowNew(env->FindClass("Ljava/lang/OutOfMemoryError;"), "Out of memory");
+ return false;
+ }
+
+ static_assert(sizeof(UChar) == sizeof(jchar), "sizeof(Uchar) != sizeof(jchar)");
+ memcpy(mUChars.get(), inputChars.get(), inputChars.size() * sizeof(jchar));
+
+ // Reset any errors that might have occurred on previous patches.
+ mStatus = U_ZERO_ERROR;
+ mUText = utext_openUChars(nullptr, mUChars.get(), inputChars.size(), &mStatus);
+ if (mUText == nullptr) {
+ CHECK(maybeThrowIcuException(env, "utext_openUChars", mStatus));
+ return false;
+ }
+
+ // It is an error for ICU to have returned a non-null mUText but to
+ // still have indicated an error.
+ CHECK(U_SUCCESS(mStatus));
+
+ mMatcher->reset(mUText);
+ return true;
+ }
+
+ ~MatcherState() {
+ if (mUText != nullptr) {
+ utext_close(mUText);
}
}
- MatcherAccessor(JNIEnv* env, jlong address) {
- init(env, address);
- }
-
- ~MatcherAccessor() {
- utext_close(mUText);
- if (mJavaInput) {
- mEnv->ReleaseStringChars(mJavaInput, mChars);
- }
- maybeThrowIcuException(mEnv, "utext_close", mStatus);
- }
-
- icu::RegexMatcher* operator->() {
- return mMatcher;
+ icu::RegexMatcher* matcher() {
+ return mMatcher.get();
}
UErrorCode& status() {
return mStatus;
}
- void updateOffsets(jintArray javaOffsets) {
- ScopedIntArrayRW offsets(mEnv, javaOffsets);
+ void updateOffsets(JNIEnv* env, jintArray javaOffsets) {
+ ScopedIntArrayRW offsets(env, javaOffsets);
if (offsets.get() == NULL) {
return;
}
@@ -96,29 +135,23 @@
}
private:
- void init(JNIEnv* env, jlong address) {
- mEnv = env;
- mJavaInput = NULL;
- mMatcher = toRegexMatcher(address);
- mChars = NULL;
- mStatus = U_ZERO_ERROR;
- mUText = NULL;
- }
-
- JNIEnv* mEnv;
- jstring mJavaInput;
- icu::RegexMatcher* mMatcher;
- const jchar* mChars;
- UErrorCode mStatus;
+ std::unique_ptr<icu::RegexMatcher> mMatcher;
+ std::unique_ptr<UChar[]> mUChars;
UText* mUText;
+ UErrorCode mStatus;
// Disallow copy and assignment.
- MatcherAccessor(const MatcherAccessor&);
- void operator=(const MatcherAccessor&);
+ MatcherState(const MatcherState&);
+ void operator=(const MatcherState&);
};
+static inline MatcherState* toMatcherState(jlong address) {
+ return reinterpret_cast<MatcherState*>(static_cast<uintptr_t>(address));
+}
+
static void Matcher_free(void* address) {
- delete reinterpret_cast<icu::RegexMatcher*>(address);
+ MatcherState* state = reinterpret_cast<MatcherState*>(address);
+ delete state;
}
static jlong Matcher_getNativeFinalizer(JNIEnv*, jclass) {
@@ -131,51 +164,48 @@
return 200; // Very rough guess based on a quick look at the implementation.
}
-static jint Matcher_findImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jint startIndex, jintArray offsets) {
- MatcherAccessor matcher(env, addr, javaText, false);
- UBool result = matcher->find(startIndex, matcher.status());
+static jint Matcher_findImpl(JNIEnv* env, jclass, jlong addr, jint startIndex, jintArray offsets) {
+ MatcherState* state = toMatcherState(addr);
+ UBool result = state->matcher()->find(startIndex, state->status());
if (result) {
- matcher.updateOffsets(offsets);
+ state->updateOffsets(env, offsets);
}
return result;
}
-static jint Matcher_findNextImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
- MatcherAccessor matcher(env, addr, javaText, false);
- if (matcher.status() != U_ZERO_ERROR) {
- return -1;
- }
- UBool result = matcher->find();
+static jint Matcher_findNextImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+ MatcherState* state = toMatcherState(addr);
+ UBool result = state->matcher()->find();
if (result) {
- matcher.updateOffsets(offsets);
+ state->updateOffsets(env, offsets);
}
return result;
}
-static jint Matcher_groupCountImpl(JNIEnv* env, jclass, jlong addr) {
- MatcherAccessor matcher(env, addr);
- return matcher->groupCount();
+static jint Matcher_groupCountImpl(JNIEnv*, jclass, jlong addr) {
+ MatcherState* state = toMatcherState(addr);
+ return state->matcher()->groupCount();
}
-static jint Matcher_hitEndImpl(JNIEnv* env, jclass, jlong addr) {
- MatcherAccessor matcher(env, addr);
- return matcher->hitEnd();
+static jint Matcher_hitEndImpl(JNIEnv*, jclass, jlong addr) {
+ MatcherState* state = toMatcherState(addr);
+ return state->matcher()->hitEnd();
}
-static jint Matcher_lookingAtImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
- MatcherAccessor matcher(env, addr, javaText, false);
- UBool result = matcher->lookingAt(matcher.status());
+static jint Matcher_lookingAtImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+ MatcherState* state = toMatcherState(addr);
+ UBool result = state->matcher()->lookingAt(state->status());
if (result) {
- matcher.updateOffsets(offsets);
+ state->updateOffsets(env, offsets);
}
return result;
}
-static jint Matcher_matchesImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jintArray offsets) {
- MatcherAccessor matcher(env, addr, javaText, false);
- UBool result = matcher->matches(matcher.status());
+static jint Matcher_matchesImpl(JNIEnv* env, jclass, jlong addr, jintArray offsets) {
+ MatcherState* state = toMatcherState(addr);
+ UBool result = state->matcher()->matches(state->status());
if (result) {
- matcher.updateOffsets(offsets);
+ state->updateOffsets(env, offsets);
}
return result;
}
@@ -184,28 +214,33 @@
icu::RegexPattern* pattern = reinterpret_cast<icu::RegexPattern*>(static_cast<uintptr_t>(patternAddr));
UErrorCode status = U_ZERO_ERROR;
icu::RegexMatcher* result = pattern->matcher(status);
- maybeThrowIcuException(env, "RegexPattern::matcher", status);
- return reinterpret_cast<uintptr_t>(result);
+ if (maybeThrowIcuException(env, "RegexPattern::matcher", status)) {
+ return 0;
+ }
+
+ return reinterpret_cast<uintptr_t>(new MatcherState(result));
}
-static jint Matcher_requireEndImpl(JNIEnv* env, jclass, jlong addr) {
- MatcherAccessor matcher(env, addr);
- return matcher->requireEnd();
+static jint Matcher_requireEndImpl(JNIEnv*, jclass, jlong addr) {
+ MatcherState* state = toMatcherState(addr);
+ return state->matcher()->requireEnd();
}
static void Matcher_setInputImpl(JNIEnv* env, jclass, jlong addr, jstring javaText, jint start, jint end) {
- MatcherAccessor matcher(env, addr, javaText, true);
- matcher->region(start, end, matcher.status());
+ MatcherState* state = toMatcherState(addr);
+ if (state->updateInput(env, javaText)) {
+ state->matcher()->region(start, end, state->status());
+ }
}
-static void Matcher_useAnchoringBoundsImpl(JNIEnv* env, jclass, jlong addr, jboolean value) {
- MatcherAccessor matcher(env, addr);
- matcher->useAnchoringBounds(value);
+static void Matcher_useAnchoringBoundsImpl(JNIEnv*, jclass, jlong addr, jboolean value) {
+ MatcherState* state = toMatcherState(addr);
+ state->matcher()->useAnchoringBounds(value);
}
-static void Matcher_useTransparentBoundsImpl(JNIEnv* env, jclass, jlong addr, jboolean value) {
- MatcherAccessor matcher(env, addr);
- matcher->useTransparentBounds(value);
+static void Matcher_useTransparentBoundsImpl(JNIEnv*, jclass, jlong addr, jboolean value) {
+ MatcherState* state = toMatcherState(addr);
+ state->matcher()->useTransparentBounds(value);
}
static jint Matcher_getMatchedGroupIndex0(JNIEnv* env, jclass, jlong patternAddr, jstring javaGroupName) {
@@ -227,13 +262,13 @@
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Matcher, getMatchedGroupIndex0, "(JLjava/lang/String;)I"),
- NATIVE_METHOD(Matcher, findImpl, "(JLjava/lang/String;I[I)Z"),
- NATIVE_METHOD(Matcher, findNextImpl, "(JLjava/lang/String;[I)Z"),
+ NATIVE_METHOD(Matcher, findImpl, "(JI[I)Z"),
+ NATIVE_METHOD(Matcher, findNextImpl, "(J[I)Z"),
NATIVE_METHOD(Matcher, getNativeFinalizer, "()J"),
NATIVE_METHOD(Matcher, groupCountImpl, "(J)I"),
NATIVE_METHOD(Matcher, hitEndImpl, "(J)Z"),
- NATIVE_METHOD(Matcher, lookingAtImpl, "(JLjava/lang/String;[I)Z"),
- NATIVE_METHOD(Matcher, matchesImpl, "(JLjava/lang/String;[I)Z"),
+ NATIVE_METHOD(Matcher, lookingAtImpl, "(J[I)Z"),
+ NATIVE_METHOD(Matcher, matchesImpl, "(J[I)Z"),
NATIVE_METHOD(Matcher, nativeSize, "()I"),
NATIVE_METHOD(Matcher, openImpl, "(J)J"),
NATIVE_METHOD(Matcher, requireEndImpl, "(J)Z"),
diff --git a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
index d30e7a3..c4f25e0 100644
--- a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
+++ b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp
@@ -46,10 +46,7 @@
}
static bool setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const icu::UnicodeString& s) {
- // Fill in whatever we got. We don't use the display names if they're "GMT[+-]xx:xx"
- // because icu4c doesn't use the up-to-date time zone transition data, so it gets these
- // wrong. TimeZone.getDisplayName creates accurate names on demand.
- // TODO: investigate whether it's worth doing that work once in the Java wrapper instead of on-demand.
+ // Don't use "GMT" string, for backwards compatibility.
static const icu::UnicodeString kGmt("GMT", 3, US_INV);
if (!s.isBogus() && !s.startsWith(kGmt)) {
ScopedLocalRef<jstring> javaString(env, env->NewString(s.getBuffer(), s.length()));
diff --git a/luni/src/test/java/libcore/java/io/FileInputStreamTest.java b/luni/src/test/java/libcore/java/io/FileInputStreamTest.java
index c199bca..b85f69a 100644
--- a/luni/src/test/java/libcore/java/io/FileInputStreamTest.java
+++ b/luni/src/test/java/libcore/java/io/FileInputStreamTest.java
@@ -264,6 +264,11 @@
assertEquals(0, Libcore.os.lseek(fis.getFD(), 0, OsConstants.SEEK_CUR));
assertEquals(lastByte, fis.skip(lastByte));
}
+
+ FileInputStream fis = new FileInputStream(largeFile);
+ long lastByte = 3 * 1024 * 1024 * 1024L - 1;
+ assertEquals(0, Libcore.os.lseek(fis.getFD(), 0, OsConstants.SEEK_CUR));
+ assertEquals(lastByte, fis.skip(lastByte));
} finally {
// Proactively cleanup - it's a pretty large file.
assertTrue(largeFile.delete());
diff --git a/luni/src/test/java/libcore/java/lang/SystemTest.java b/luni/src/test/java/libcore/java/lang/SystemTest.java
index 2de659d..48f4591 100644
--- a/luni/src/test/java/libcore/java/lang/SystemTest.java
+++ b/luni/src/test/java/libcore/java/lang/SystemTest.java
@@ -16,17 +16,16 @@
package libcore.java.lang;
+import junit.framework.TestCase;
+
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.lang.SecurityException;
-import java.lang.SecurityManager;
import java.util.Formatter;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
-import junit.framework.TestCase;
public class SystemTest extends TestCase {
@@ -250,14 +249,4 @@
} catch (SecurityException expected) {
}
}
-
- // http://b/34867424
- public void testIcuPathIncludesTimeZoneOverride() {
- String icuDataPath = System.getProperty("android.icu.impl.ICUBinary.dataPath");
- String[] paths = icuDataPath.split(":");
- assertEquals(2, paths.length);
-
- assertTrue(paths[0].contains("/misc/zoneinfo/current/icu"));
- assertTrue(paths[1].contains("/usr/icu"));
- }
}
diff --git a/luni/src/test/java/libcore/java/net/URLTest.java b/luni/src/test/java/libcore/java/net/URLTest.java
index 3a14063..231e09c 100644
--- a/luni/src/test/java/libcore/java/net/URLTest.java
+++ b/luni/src/test/java/libcore/java/net/URLTest.java
@@ -194,6 +194,14 @@
assertEquals(null, url.getRef());
}
+ // This behavior of URLs with invalid user info changed due to bug http://b/33351987 after
+ // Android security level 1 April 2017.
+ public void testAtSignInUserInfo() throws Exception {
+ URL url = new URL("http://user@userhost.com:password@host");
+ assertNull(url.getUserInfo());
+ assertTrue(url.getHost().isEmpty());
+ }
+
public void testUserNoPassword() throws Exception {
URL url = new URL("http://user@host");
assertEquals("user@host", url.getAuthority());
@@ -755,6 +763,28 @@
assertEquals("/some/path", new URL("http://foobar.com/some/path?#").getFile());
}
+ // http://b/31858037
+ public void testFragmentWithSlash() throws Exception {
+ final String host = "example.com";
+ final String fragment = "@not-a-host-name/a";
+ URL url = new URL(String.format("http://%s#%s", host, fragment));
+ assertNull(url.getUserInfo());
+ assertEquals(host, url.getAuthority());
+ assertEquals(host, url.getHost());
+ assertEquals(fragment, url.getRef());
+ }
+
+ // http://b/31858037
+ public void testFragmentWithQuery() throws Exception {
+ final String host = "example.com";
+ final String fragment = "@not-a-host-name?a";
+ URL url = new URL(String.format("http://%s#%s", host, fragment));
+ assertNull(url.getUserInfo());
+ assertEquals(host, url.getAuthority());
+ assertEquals(host, url.getHost());
+ assertEquals(fragment, url.getRef());
+ }
+
// http://b/33351987
public void testMultipleUserField() throws Exception {
final String host = "http://multiple@users@url.com";
diff --git a/luni/src/test/java/libcore/java/nio/file/DefaultFileStoreTest.java b/luni/src/test/java/libcore/java/nio/file/DefaultFileStoreTest.java
deleted file mode 100644
index 763efbf..0000000
--- a/luni/src/test/java/libcore/java/nio/file/DefaultFileStoreTest.java
+++ /dev/null
@@ -1,166 +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.java.nio.file;
-
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.DosFileAttributeView;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileOwnerAttributeView;
-import java.nio.file.attribute.FileStoreAttributeView;
-import java.nio.file.attribute.PosixFileAttributeView;
-import java.nio.file.attribute.UserDefinedFileAttributeView;
-
-import static junit.framework.TestCase.assertFalse;
-import static junit.framework.TestCase.assertNull;
-import static junit.framework.TestCase.assertTrue;
-import static libcore.java.nio.file.FilesSetup.execCmdAndWaitForTermination;
-import static libcore.java.nio.file.FilesSetup.readFromInputStream;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-public class DefaultFileStoreTest {
-
- @Rule
- public FilesSetup filesSetup = new FilesSetup();
-
- @Test
- public void test_name() throws IOException, InterruptedException {
- Path path = filesSetup.getPathInTestDir("dir");
- Files.createDirectory(path);
- Process p = execCmdAndWaitForTermination("df", path.toAbsolutePath().toString());
- String shellOutput = readFromInputStream(p.getInputStream()).split("\n")[1];
- String storeTypeFromShell = shellOutput.split("\\s")[0];
- assertEquals(storeTypeFromShell, Files.getFileStore(path).name());
- }
-
- @Test
- public void test_type() throws IOException, InterruptedException {
- Path path = filesSetup.getPathInTestDir("dir");
- Files.createDirectory(path);
- String fileStore = Files.getFileStore(path).name();
- Process p = execCmdAndWaitForTermination("mount");
- String mountOutput[] = readFromInputStream(p.getInputStream()).split("\n");
- for (String mountInfo : mountOutput) {
- if (mountInfo.contains(fileStore)
- && mountInfo.contains(Files.getFileStore(path).type())) {
- return;
- }
- }
- fail();
- }
-
- @Test
- public void test_isReadOnly() throws IOException {
- Path path = Paths.get("/system");
- assertTrue(Files.getFileStore(path).isReadOnly());
-
- path = Paths.get("/data");
- assertFalse(Files.getFileStore(path).isReadOnly());
- }
-
- @Test
- public void test_getTotalSpace() throws IOException {
- Path path = Paths.get("/data");
- assertTrue(Files.getFileStore(path).getTotalSpace() > 0);
- }
-
- @Test
- public void test_getUsableSpace() throws IOException {
- Path path = Paths.get("/data");
- long usableSpace = Files.getFileStore(path).getUsableSpace();
- long totalSpace = Files.getFileStore(path).getTotalSpace();
- assertTrue(usableSpace <= totalSpace && usableSpace > 0);
- }
-
- @Test
- public void test_getUnallocatedSpace() throws IOException {
- Path path = Paths.get("/data");
- long unallocatedSpace = Files.getFileStore(path).getUnallocatedSpace();
- assertTrue(unallocatedSpace >= 0);
- }
-
- @Test
- public void test_supportsFileAttributeView$Class() throws IOException {
- Path path = filesSetup.getPathInTestDir("dir");
- Files.createDirectories(path);
- assertTrue(Files.getFileStore(path).supportsFileAttributeView(
- BasicFileAttributeView.class));
- assertTrue(Files.getFileStore(path).supportsFileAttributeView(
- FileOwnerAttributeView.class));
- assertTrue(Files.getFileStore(path).supportsFileAttributeView(
- PosixFileAttributeView.class));
- assertFalse(Files.getFileStore(path).supportsFileAttributeView(DosFileAttributeView.class));
- assertFalse(Files.getFileStore(path).
- supportsFileAttributeView(UserDefinedFileAttributeView.class));
- assertFalse(Files.getFileStore(path).
- supportsFileAttributeView(NonStandardFileAttributeView.class));
- }
-
- @Test
- public void test_supportsFileAttributeView$String() throws IOException {
- Path path = filesSetup.getPathInTestDir("dir");
- Files.createDirectories(path);
- assertTrue(Files.getFileStore(path).supportsFileAttributeView("basic"));
- assertTrue(Files.getFileStore(path).supportsFileAttributeView("unix"));
- assertTrue(Files.getFileStore(path).supportsFileAttributeView("posix"));
- assertTrue(Files.getFileStore(path).supportsFileAttributeView("owner"));
- assertFalse(Files.getFileStore(path).supportsFileAttributeView("user"));
- assertFalse(Files.getFileStore(path).supportsFileAttributeView("dos"));
- assertFalse(Files.getFileStore(path).supportsFileAttributeView("nonStandardView"));
- }
-
- @Test
- public void test_getFileStoreAttributeView() throws IOException {
- Path path = filesSetup.getPathInTestDir("dir");
- Files.createDirectories(path);
- assertNull(Files.getFileStore(path).getFileStoreAttributeView(
- FileStoreAttributeView.class));
- try {
- Files.getFileStore(path).getFileStoreAttributeView(null);
- fail();
- } catch (NullPointerException expected) {}
- }
-
- @Test
- public void test_getAttribute() throws IOException {
- Path p = filesSetup.getPathInTestDir("dir");
- Files.createDirectories(p);
- FileStore store = Files.getFileStore(p);
- assertEquals(store.getTotalSpace(), store.getAttribute("totalSpace"));
- assertEquals(store.getUnallocatedSpace(), store.getAttribute("unallocatedSpace"));
- assertEquals(store.getUsableSpace(), store.getAttribute("usableSpace"));
- try {
- store.getAttribute("test");
- fail();
- } catch (UnsupportedOperationException expected) {}
- }
-
- private static class NonStandardFileAttributeView implements FileAttributeView {
- @Override
- public String name() {
- return null;
- }
- }
-}
\ No newline at end of file
diff --git a/luni/src/test/java/libcore/java/nio/file/DefaultFileSystemProvider2Test.java b/luni/src/test/java/libcore/java/nio/file/DefaultFileSystemProvider2Test.java
index dbbfeae..2916b03 100644
--- a/luni/src/test/java/libcore/java/nio/file/DefaultFileSystemProvider2Test.java
+++ b/luni/src/test/java/libcore/java/nio/file/DefaultFileSystemProvider2Test.java
@@ -263,24 +263,35 @@
@Test
public void test_getFileStore() throws IOException {
- FileStore fileStore = provider.getFileStore(filesSetup.getDataFilePath());
- assertNotNull(fileStore);
- }
+ try {
+ provider.getFileStore(filesSetup.getDataFilePath());
+ fail();
+ } catch (SecurityException expected) {
+ }
- @Test
- public void test_getFileStore_NPE() throws IOException {
try {
provider.getFileStore(null);
fail();
- } catch(NullPointerException expected) {}
+ } catch (SecurityException expected) {
+ }
}
@Test
public void test_isHidden() throws IOException {
assertFalse(provider.isHidden(filesSetup.getDataFilePath()));
- Files.setAttribute(filesSetup.getDataFilePath(), "dos:hidden", true);
- // Files can't be hid.
+ // Files can't be hidden using the "dos" view, which is unsupported since it relies
+ // on a custom xattr, which may or may not be available on all FSs.
+ //
+ // Note that this weirdly asymmetric : setting the hidden attribute uses xattrs to
+ // emulate dos attributes whereas isHidden checks whether the the file name begins with a
+ // leading period. <shrug>
+ try {
+ Files.setAttribute(filesSetup.getDataFilePath(), "dos:hidden", true);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
assertFalse(provider.isHidden(filesSetup.getDataFilePath()));
}
diff --git a/luni/src/test/java/libcore/java/nio/file/Files2Test.java b/luni/src/test/java/libcore/java/nio/file/Files2Test.java
index 394643a..436d3e3 100644
--- a/luni/src/test/java/libcore/java/nio/file/Files2Test.java
+++ b/luni/src/test/java/libcore/java/nio/file/Files2Test.java
@@ -138,9 +138,12 @@
@Test
public void test_getFileStore() throws IOException {
- FileStore mockFileStore = mock(FileStore.class);
- when(mockFileSystemProvider.getFileStore(mockPath)).thenReturn(mockFileStore);
- assertEquals(mockFileStore, Files.getFileStore(mockPath));
+ when(mockFileSystemProvider.getFileStore(mockPath)).thenThrow(new SecurityException());
+ try {
+ Files.getFileStore(mockPath);
+ fail();
+ } catch (SecurityException expected) {
+ }
}
@Test
diff --git a/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java
index 26afa1d..34c6aab 100644
--- a/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java
+++ b/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java
@@ -124,7 +124,7 @@
// It is expected that the symbols may change with future CLDR updates.
dfs = new DecimalFormatSymbols(Locale.forLanguageTag("ar"));
- assertEquals('%', dfs.getPercent());
+ assertEquals('Ùª', dfs.getPercent());
assertEquals('-', dfs.getMinusSign());
dfs = new DecimalFormatSymbols(Locale.forLanguageTag("fa"));
@@ -186,4 +186,26 @@
compareDfs(dfs, icuSymb);
}
+ // http://b/36562145
+ public void testMaybeStripMarkers() {
+ final char ltr = '\u200E';
+ final char rtl = '\u200F';
+ final char alm = '\u061C';
+ final char fallback = 'F';
+ assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("", fallback));
+ assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("XY", fallback));
+ assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("" + ltr, fallback));
+ assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("" + rtl, fallback));
+ assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers("" + alm, fallback));
+ assertEquals(fallback,
+ DecimalFormatSymbols.maybeStripMarkers("X" + ltr + rtl + alm + "Y", fallback));
+ assertEquals(fallback,
+ DecimalFormatSymbols.maybeStripMarkers("" + ltr + rtl + alm, fallback));
+ assertEquals(fallback, DecimalFormatSymbols.maybeStripMarkers(alm + "XY" + rtl, fallback));
+ assertEquals('X', DecimalFormatSymbols.maybeStripMarkers("X", fallback));
+ assertEquals('X', DecimalFormatSymbols.maybeStripMarkers("X" + ltr, fallback));
+ assertEquals('X', DecimalFormatSymbols.maybeStripMarkers("X" + rtl, fallback));
+ assertEquals('X', DecimalFormatSymbols.maybeStripMarkers(alm + "X", fallback));
+ assertEquals('X', DecimalFormatSymbols.maybeStripMarkers(alm + "X" + rtl, fallback));
+ }
}
diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
index 6b692cf..375eb36 100644
--- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java
+++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java
@@ -216,15 +216,26 @@
// http://b/7955614 and http://b/8026776.
public void testDisplayNames() throws Exception {
+ checkDisplayNames(Locale.US);
+ }
+
+ public void testDisplayNames_nonUS() throws Exception {
+ // run checkDisplayNames with an arbitrary set of Locales.
+ checkDisplayNames(Locale.CHINESE);
+ checkDisplayNames(Locale.FRENCH);
+ checkDisplayNames(Locale.forLanguageTag("bn-BD"));
+ }
+
+ public void checkDisplayNames(Locale locale) throws Exception {
// Check that there are no time zones that use DST but have the same display name for
// both standard and daylight time.
StringBuilder failures = new StringBuilder();
for (String id : TimeZone.getAvailableIDs()) {
TimeZone tz = TimeZone.getTimeZone(id);
- String longDst = tz.getDisplayName(true, TimeZone.LONG, Locale.US);
- String longStd = tz.getDisplayName(false, TimeZone.LONG, Locale.US);
- String shortDst = tz.getDisplayName(true, TimeZone.SHORT, Locale.US);
- String shortStd = tz.getDisplayName(false, TimeZone.SHORT, Locale.US);
+ String longDst = tz.getDisplayName(true, TimeZone.LONG, locale);
+ String longStd = tz.getDisplayName(false, TimeZone.LONG, locale);
+ String shortDst = tz.getDisplayName(true, TimeZone.SHORT, locale);
+ String shortStd = tz.getDisplayName(false, TimeZone.SHORT, locale);
if (tz.useDaylightTime()) {
// The long std and dst strings must differ!
@@ -329,15 +340,6 @@
return String.format("GMT%c%02d:%02d", sign, offset / 60, offset % 60);
}
- public void testAllDisplayNames() throws Exception {
- for (Locale locale : Locale.getAvailableLocales()) {
- for (String id : TimeZone.getAvailableIDs()) {
- TimeZone tz = TimeZone.getTimeZone(id);
- assertNotNull(tz.getDisplayName(false, TimeZone.LONG, locale));
- }
- }
- }
-
// http://b/18839557
public void testOverflowing32BitUnixDates() {
final TimeZone tz = TimeZone.getTimeZone("America/New_York");
diff --git a/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java b/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java
new file mode 100644
index 0000000..dd9a9a0
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/crypto/CipherOutputStreamTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.javax.crypto;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.crypto.AEADBadTagException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+public final class CipherOutputStreamTest extends TestCase {
+
+ // From b/36636576. CipherOutputStream had a bug where it would ignore exceptions
+ // thrown during close().
+ public void testDecryptCorruptGCM() throws Exception {
+ for (Provider provider : Security.getProviders()) {
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance("AES/GCM/NoPadding", provider);
+ } catch (NoSuchAlgorithmException e) {
+ continue;
+ }
+ SecretKey key;
+ if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) {
+ key = getAndroidKeyStoreSecretKey();
+ } else {
+ KeyGenerator keygen = KeyGenerator.getInstance("AES");
+ keygen.init(256);
+ key = keygen.generateKey();
+ }
+ GCMParameterSpec params = new GCMParameterSpec(128, new byte[12]);
+ byte[] unencrypted = new byte[200];
+
+ // Normal providers require specifying the IV, but KeyStore prohibits it, so
+ // we have to special-case it
+ if (provider.getName().equals("AndroidKeyStoreBCWorkaround")) {
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ } else {
+ cipher.init(Cipher.ENCRYPT_MODE, key, params);
+ }
+ byte[] encrypted = cipher.doFinal(unencrypted);
+
+ // Corrupt the final byte, which will corrupt the authentication tag
+ encrypted[encrypted.length - 1] ^= 1;
+
+ cipher.init(Cipher.DECRYPT_MODE, key, params);
+ CipherOutputStream cos = new CipherOutputStream(new ByteArrayOutputStream(), cipher);
+ try {
+ cos.write(encrypted);
+ cos.close();
+ fail("Writing a corrupted stream should throw an exception."
+ + " Provider: " + provider);
+ } catch (IOException expected) {
+ assertTrue(expected.getCause() instanceof AEADBadTagException);
+ }
+ }
+
+ }
+
+ // The AndroidKeyStoreBCWorkaround provider can't use keys created by anything
+ // but Android KeyStore, which requires using its own parameters class to create
+ // keys. Since we're in javax, we can't link against the frameworks classes, so
+ // we have to use reflection to make a suitable key. This will always be safe
+ // because if we're making a key for AndroidKeyStoreBCWorkaround, the KeyStore
+ // classes must be present.
+ private static SecretKey getAndroidKeyStoreSecretKey() throws Exception {
+ KeyGenerator keygen = KeyGenerator.getInstance("AES", "AndroidKeyStore");
+ Class<?> keyParamsBuilderClass = keygen.getClass().getClassLoader().loadClass(
+ "android.security.keystore.KeyGenParameterSpec$Builder");
+ Object keyParamsBuilder = keyParamsBuilderClass.getConstructor(String.class, Integer.TYPE)
+ // 3 is PURPOSE_ENCRYPT | PURPOSE_DECRYPT
+ .newInstance("testDecryptCorruptGCM", 3);
+ keyParamsBuilderClass.getMethod("setBlockModes", new Class[]{String[].class})
+ .invoke(keyParamsBuilder, new Object[]{new String[]{"GCM"}});
+ keyParamsBuilderClass.getMethod("setEncryptionPaddings", new Class[]{String[].class})
+ .invoke(keyParamsBuilder, new Object[]{new String[]{"NoPadding"}});
+ AlgorithmParameterSpec spec = (AlgorithmParameterSpec)
+ keyParamsBuilderClass.getMethod("build", new Class[]{}).invoke(keyParamsBuilder);
+ keygen.init(spec);
+ return keygen.generateKey();
+ }
+}
diff --git a/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java b/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java
new file mode 100644
index 0000000..efba900
--- /dev/null
+++ b/luni/src/test/java/libcore/util/TimeZoneDataFilesTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TimeZoneDataFilesTest {
+
+ @Test
+ public void getTimeZoneFilePaths() {
+ String[] paths = TimeZoneDataFiles.getTimeZoneFilePaths("foo");
+ assertEquals(2, paths.length);
+
+ assertTrue(paths[0].contains("/misc/zoneinfo/current/"));
+ assertTrue(paths[0].endsWith("foo"));
+
+ assertTrue(paths[1].contains("/usr/share/zoneinfo/"));
+ assertTrue(paths[1].endsWith("foo"));
+ }
+
+ // http://b/34867424
+ @Test
+ public void generateIcuDataPath_includesTimeZoneOverride() {
+ String icuDataPath = System.getProperty("android.icu.impl.ICUBinary.dataPath");
+ assertEquals(icuDataPath, TimeZoneDataFiles.generateIcuDataPath());
+
+ String[] paths = icuDataPath.split(":");
+ assertEquals(2, paths.length);
+
+ assertTrue(paths[0].contains("/misc/zoneinfo/current/icu"));
+ assertTrue(paths[1].contains("/usr/icu"));
+ }
+}
diff --git a/luni/src/test/java/libcore/util/TimeZoneFinderTest.java b/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
new file mode 100644
index 0000000..0b31c9a
--- /dev/null
+++ b/luni/src/test/java/libcore/util/TimeZoneFinderTest.java
@@ -0,0 +1,729 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.icu.util.TimeZone;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class TimeZoneFinderTest {
+
+ private static final int HOUR_MILLIS = 60 * 60 * 1000;
+
+ // Zones used in the tests. NEW_YORK_TZ and LONDON_TZ chosen because they never overlap but both
+ // have DST.
+ private static final TimeZone NEW_YORK_TZ = TimeZone.getTimeZone("America/New_York");
+ private static final TimeZone LONDON_TZ = TimeZone.getTimeZone("Europe/London");
+ // A zone that matches LONDON_TZ for WHEN_NO_DST. It does not have DST so differs for WHEN_DST.
+ private static final TimeZone REYKJAVIK_TZ = TimeZone.getTimeZone("Atlantic/Reykjavik");
+ // Another zone that matches LONDON_TZ for WHEN_NO_DST. It does not have DST so differs for
+ // WHEN_DST.
+ private static final TimeZone UTC_TZ = TimeZone.getTimeZone("Etc/UTC");
+
+ // 22nd July 2017, 13:14:15 UTC (DST time in all the timezones used in these tests that observe
+ // DST).
+ private static final long WHEN_DST = 1500729255000L;
+ // 22nd January 2018, 13:14:15 UTC (non-DST time in all timezones used in these tests).
+ private static final long WHEN_NO_DST = 1516626855000L;
+
+ private static final int LONDON_DST_OFFSET_MILLIS = HOUR_MILLIS;
+ private static final int LONDON_NO_DST_OFFSET_MILLIS = 0;
+
+ private static final int NEW_YORK_DST_OFFSET_MILLIS = -4 * HOUR_MILLIS;
+ private static final int NEW_YORK_NO_DST_OFFSET_MILLIS = -5 * HOUR_MILLIS;
+
+ private Path testDir;
+
+ @Before
+ public void setUp() throws Exception {
+ testDir = Files.createTempDirectory("TimeZoneFinderTest");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Delete the testDir and all contents.
+ Files.walkFileTree(testDir, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc)
+ throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ @Test
+ public void createInstanceWithFallback() throws Exception {
+ String validXml1 = "<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n";
+ String validXml2 = "<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/Paris</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n";
+
+ String invalidXml = "<foo></foo>\n";
+ checkValidateThrowsParserException(invalidXml);
+
+ String validFile1 = createFile(validXml1);
+ String validFile2 = createFile(validXml2);
+ String invalidFile = createFile(invalidXml);
+ String missingFile = createMissingFile();
+
+ TimeZoneFinder file1ThenFile2 =
+ TimeZoneFinder.createInstanceWithFallback(validFile1, validFile2);
+ assertZonesEqual(zones("Europe/London"), file1ThenFile2.lookupTimeZonesByCountry("gb"));
+
+ TimeZoneFinder missingFileThenFile1 =
+ TimeZoneFinder.createInstanceWithFallback(missingFile, validFile1);
+ assertZonesEqual(zones("Europe/London"),
+ missingFileThenFile1.lookupTimeZonesByCountry("gb"));
+
+ TimeZoneFinder file2ThenFile1 =
+ TimeZoneFinder.createInstanceWithFallback(validFile2, validFile1);
+ 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.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.lookupTimeZonesByCountry("gb"));
+ }
+
+ @Test
+ public void xmlParsing_emptyFile() throws Exception {
+ checkValidateThrowsParserException("");
+ }
+
+ @Test
+ public void xmlParsing_unexpectedRootElement() throws Exception {
+ checkValidateThrowsParserException("<foo></foo>\n");
+ }
+
+ @Test
+ public void xmlParsing_missingCountryZones() throws Exception {
+ checkValidateThrowsParserException("<timezones></timezones>\n");
+ }
+
+ @Test
+ public void xmlParsing_noCountriesOk() throws Exception {
+ validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ }
+
+ @Test
+ public void xmlParsing_unexpectedComments() throws Exception {
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <!-- This is a comment -->"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+ // This is a crazy comment, but also helps prove that TEXT nodes are coalesced by the
+ // parser.
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/<!-- Don't freak out! -->London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ }
+
+ @Test
+ public void xmlParsing_unexpectedElementsIgnored() throws Exception {
+ String unexpectedElement = "<unexpected-element>\n<a /></unexpected-element>\n";
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " " + unexpectedElement
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " " + unexpectedElement
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " " + unexpectedElement
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\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"));
+
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " " + unexpectedElement
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+ // This test is important because it ensures we can extend the format in future with
+ // more information.
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + " " + unexpectedElement
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ }
+
+ @Test
+ public void xmlParsing_unexpectedTextIgnored() throws Exception {
+ String unexpectedText = "unexpected-text";
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " " + unexpectedText
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " " + unexpectedText
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " " + unexpectedText
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+
+ finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\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"));
+ }
+
+ @Test
+ public void xmlParsing_truncatedInput() throws Exception {
+ checkValidateThrowsParserException("<timezones>\n");
+
+ checkValidateThrowsParserException("<timezones>\n"
+ + " <countryzones>\n");
+
+ checkValidateThrowsParserException("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n");
+
+ checkValidateThrowsParserException("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n");
+
+ checkValidateThrowsParserException("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n");
+
+ checkValidateThrowsParserException("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n");
+ }
+
+ @Test
+ public void xmlParsing_unexpectedChildInTimeZoneIdThrows() throws Exception {
+ checkValidateThrowsParserException("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id><unexpected-element /></id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ }
+
+ @Test
+ public void xmlParsing_unknownTimeZoneIdIgnored() throws Exception {
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Unknown_Id</id>\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertZonesEqual(zones("Europe/London"), finder.lookupTimeZonesByCountry("gb"));
+ }
+
+ @Test
+ public void xmlParsing_missingCountryCode() throws Exception {
+ checkValidateThrowsParserException("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country>\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ }
+
+ @Test
+ public void xmlParsing_unknownCountryReturnsNull() throws Exception {
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+ assertNull(finder.lookupTimeZonesByCountry("gb"));
+ }
+
+ @Test
+ public void lookupTimeZonesByCountry_structuresAreImmutable() throws Exception {
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+
+ List<TimeZone> gbList = finder.lookupTimeZonesByCountry("gb");
+ assertEquals(1, gbList.size());
+ assertImmutableList(gbList);
+ assertImmutableTimeZone(gbList.get(0));
+
+ assertNull(finder.lookupTimeZonesByCountry("unknown"));
+ }
+
+ @Test
+ public void lookupTimeZoneByCountryAndOffset_unknownCountry() throws Exception {
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"xx\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+
+ // Demonstrate the arguments work for a known country.
+ 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,
+ LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
+
+ assertNull(finder.lookupTimeZoneByCountryAndOffset(unknownCountryCode,
+ LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
+ }
+
+ @Test
+ public void lookupTimeZoneByCountryAndOffset_oneCandidate() throws Exception {
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"xx\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+
+ // The three parameters match the configured zone: offset, isDst and when.
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_DST, null /* bias */));
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
+ false /* isDst */, WHEN_NO_DST, null /* bias */));
+
+ // Some lookup failure cases where the offset, isDst and when do not match the configured
+ // zone.
+ TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
+ assertNull(noDstMatch1);
+
+ TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */);
+ assertNull(noDstMatch2);
+
+ TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */);
+ assertNull(noDstMatch3);
+
+ TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
+ assertNull(noDstMatch4);
+
+ TimeZone noDstMatch5 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
+ assertNull(noDstMatch5);
+
+ TimeZone noDstMatch6 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
+ assertNull(noDstMatch6);
+
+ // Some bias cases below.
+
+ // The bias is irrelevant here: it matches what would be returned anyway.
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
+ false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+ // A sample of a non-matching case with bias.
+ assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+
+ // The bias should be ignored: it doesn't match any of the country's zones.
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
+
+ // The bias should still be ignored even though it matches the offset information given:
+ // it doesn't match any of the country's configured zones.
+ assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", NEW_YORK_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
+ }
+
+ @Test
+ public void lookupTimeZoneByCountryAndOffset_multipleNonOverlappingCandidates()
+ throws Exception {
+ TimeZoneFinder finder = validate("<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"xx\">\n"
+ + " <id>America/New_York</id>\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+
+ // The three parameters match the configured zone: offset, isDst and when.
+ assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
+ assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */));
+ assertZoneEquals(NEW_YORK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+ NEW_YORK_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */));
+ assertZoneEquals(NEW_YORK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+ NEW_YORK_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */));
+
+ // Some lookup failure cases where the offset, isDst and when do not match the configured
+ // zone. This is a sample, not complete.
+ TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
+ assertNull(noDstMatch1);
+
+ TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_NO_DST, null /* bias */);
+ assertNull(noDstMatch2);
+
+ TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ NEW_YORK_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, null /* bias */);
+ assertNull(noDstMatch3);
+
+ TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ NEW_YORK_NO_DST_OFFSET_MILLIS, true /* isDst */, WHEN_NO_DST, null /* bias */);
+ assertNull(noDstMatch4);
+
+ TimeZone noDstMatch5 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
+ assertNull(noDstMatch5);
+
+ TimeZone noDstMatch6 = finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_NO_DST_OFFSET_MILLIS, false /* isDst */, WHEN_DST, null /* bias */);
+ assertNull(noDstMatch6);
+
+ // Some bias cases below.
+
+ // The bias is irrelevant here: it matches what would be returned anyway.
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_DST, LONDON_TZ /* bias */));
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_NO_DST_OFFSET_MILLIS,
+ false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+ // A sample of a non-matching case with bias.
+ assertNull(finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+
+ // The bias should be ignored: it matches a configured zone, but the offset is wrong so
+ // should not be considered a match.
+ assertZoneEquals(LONDON_TZ,
+ finder.lookupTimeZoneByCountryAndOffset("xx", LONDON_DST_OFFSET_MILLIS,
+ true /* isDst */, WHEN_DST, NEW_YORK_TZ /* bias */));
+ }
+
+ // This is an artificial case very similar to America/Denver and America/Phoenix in the US: both
+ // have the same offset for 6 months of the year but diverge. Australia/Lord_Howe too.
+ @Test
+ 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"
+ + " <countryzones>\n"
+ + " <country code=\"xx\">\n"
+ + " <id>Atlantic/Reykjavik</id>\n"
+ + " <id>Europe/London</id>\n"
+ + " <id>Etc/UTC</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n");
+
+ // This is the no-DST offset for LONDON_TZ, REYKJAVIK_TZ. UTC_TZ.
+ final int noDstOffset = LONDON_NO_DST_OFFSET_MILLIS;
+ // This is the DST offset for LONDON_TZ.
+ final int dstOffset = LONDON_DST_OFFSET_MILLIS;
+
+ // The three parameters match the configured zone: offset, isDst and when.
+ assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
+ true /* isDst */, WHEN_DST, null /* bias */));
+ assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+ false /* isDst */, WHEN_NO_DST, null /* bias */));
+ assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
+ true /* isDst */, WHEN_DST, null /* bias */));
+ assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+ false /* isDst */, WHEN_NO_DST, null /* bias */));
+ assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+ false /* isDst */, WHEN_DST, null /* bias */));
+
+ // Some lookup failure cases where the offset, isDst and when do not match the configured
+ // zones.
+ TimeZone noDstMatch1 = finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
+ true /* isDst */, WHEN_NO_DST, null /* bias */);
+ assertNull(noDstMatch1);
+
+ TimeZone noDstMatch2 = finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+ true /* isDst */, WHEN_DST, null /* bias */);
+ assertNull(noDstMatch2);
+
+ TimeZone noDstMatch3 = finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+ true /* isDst */, WHEN_NO_DST, null /* bias */);
+ assertNull(noDstMatch3);
+
+ TimeZone noDstMatch4 = finder.lookupTimeZoneByCountryAndOffset("xx", dstOffset,
+ false /* isDst */, WHEN_DST, null /* bias */);
+ assertNull(noDstMatch4);
+
+
+ // Some bias cases below.
+
+ // The bias is relevant here: it overrides what would be returned naturally.
+ assertZoneEquals(REYKJAVIK_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+ false /* isDst */, WHEN_NO_DST, null /* bias */));
+ assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+ false /* isDst */, WHEN_NO_DST, LONDON_TZ /* bias */));
+ assertZoneEquals(UTC_TZ, finder.lookupTimeZoneByCountryAndOffset("xx", noDstOffset,
+ false /* isDst */, WHEN_NO_DST, UTC_TZ /* bias */));
+
+ // The bias should be ignored: it matches a configured zone, but the offset is wrong so
+ // should not be considered a match.
+ assertZoneEquals(LONDON_TZ, finder.lookupTimeZoneByCountryAndOffset("xx",
+ LONDON_DST_OFFSET_MILLIS, true /* isDst */, WHEN_DST, REYKJAVIK_TZ /* bias */));
+ }
+
+ @Test
+ public void consistencyTest() throws Exception {
+ // Confirm that no new zones have been added to zones.tab without also adding them to the
+ // configuration used to drive TimeZoneFinder.
+
+ // zone.tab is a tab separated ASCII file provided by IANA and included in Android's tzdata
+ // file. Each line contains a mapping from country code -> zone ID. The ordering used by
+ // TimeZoneFinder is Android-specific, but we can use zone.tab to make sure we know about
+ // all country zones. Any update to tzdata that adds, renames, or removes zones should be
+ // reflected in the file used by TimeZoneFinder.
+ Map<String, Set<String>> zoneTabMappings = new HashMap<>();
+ for (String line : ZoneInfoDB.getInstance().getZoneTab().split("\n")) {
+ int countryCodeEnd = line.indexOf('\t', 1);
+ int olsonIdStart = line.indexOf('\t', 4) + 1;
+ int olsonIdEnd = line.indexOf('\t', olsonIdStart);
+ if (olsonIdEnd == -1) {
+ olsonIdEnd = line.length(); // Not all zone.tab lines have a comment.
+ }
+ String countryCode = line.substring(0, countryCodeEnd);
+ String olsonId = line.substring(olsonIdStart, olsonIdEnd);
+ Set<String> zoneIds = zoneTabMappings.get(countryCode);
+ if (zoneIds == null) {
+ zoneIds = new HashSet<>();
+ zoneTabMappings.put(countryCode, zoneIds);
+ }
+ zoneIds.add(olsonId);
+ }
+
+ TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
+ for (Map.Entry<String, Set<String>> countryEntry : zoneTabMappings.entrySet()) {
+ String countryCode = countryEntry.getKey();
+ // Android uses lower case, IANA uses upper.
+ countryCode = countryCode.toLowerCase();
+
+ List<String> ianaZoneIds = countryEntry.getValue().stream().sorted()
+ .collect(Collectors.toList());
+ List<TimeZone> androidZones = timeZoneFinder.lookupTimeZonesByCountry(countryCode);
+ List<String> androidZoneIds =
+ androidZones.stream().map(TimeZone::getID).sorted()
+ .collect(Collectors.toList());
+
+ assertEquals("Android zones for " + countryCode + " do not match IANA data",
+ ianaZoneIds, androidZoneIds);
+ }
+ }
+
+ private void assertImmutableTimeZone(TimeZone timeZone) {
+ try {
+ timeZone.setRawOffset(1000);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ private static void assertImmutableList(List<TimeZone> timeZones) {
+ try {
+ timeZones.add(null);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ private static void assertZoneEquals(TimeZone expected, TimeZone actual) {
+ // TimeZone.equals() only checks the ID, but that's ok for these tests.
+ assertEquals(expected, actual);
+ }
+
+ private static void assertZonesEqual(List<TimeZone> expected, List<TimeZone> actual) {
+ // TimeZone.equals() only checks the ID, but that's ok for these tests.
+ assertEquals(expected, actual);
+ }
+
+ private static void checkValidateThrowsParserException(String xml) throws Exception {
+ try {
+ validate(xml);
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ private static TimeZoneFinder validate(String xml) throws IOException {
+ TimeZoneFinder timeZoneFinder = TimeZoneFinder.createInstanceForTests(xml);
+ timeZoneFinder.validate();
+ return timeZoneFinder;
+ }
+
+ private static List<TimeZone> zones(String... ids) {
+ return Arrays.stream(ids).map(TimeZone::getTimeZone).collect(Collectors.toList());
+ }
+
+ private String createFile(String fileContent) throws IOException {
+ Path filePath = Files.createTempFile(testDir, null, null);
+ Files.write(filePath, fileContent.getBytes(StandardCharsets.UTF_8));
+ return filePath.toString();
+ }
+
+ private String createMissingFile() throws IOException {
+ Path filePath = Files.createTempFile(testDir, null, null);
+ Files.delete(filePath);
+ return filePath.toString();
+ }
+}
diff --git a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
index 901519e..e6ec4df 100644
--- a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
+++ b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
@@ -28,27 +28,27 @@
public class ZoneInfoDBTest extends junit.framework.TestCase {
// The base tzdata file, always present on a device.
- private static final String TZDATA_IN_ROOT =
- System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata";
+ private static final String SYSTEM_TZDATA_FILE =
+ TimeZoneDataFiles.getSystemTimeZoneFile(ZoneInfoDB.TZDATA_FILE);
// An empty override file should fall back to the default file.
public void testLoadTzDataWithFallback_emptyOverrideFile() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
String emptyFilePath = makeEmptyFile().getPath();
ZoneInfoDB.TzData dataWithEmptyOverride =
- ZoneInfoDB.TzData.loadTzDataWithFallback(emptyFilePath, TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData.loadTzDataWithFallback(emptyFilePath, SYSTEM_TZDATA_FILE);
assertEquals(data.getVersion(), dataWithEmptyOverride.getVersion());
assertEquals(data.getAvailableIDs().length, dataWithEmptyOverride.getAvailableIDs().length);
}
// A corrupt override file should fall back to the default file.
public void testLoadTzDataWithFallback_corruptOverrideFile() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
String corruptFilePath = makeCorruptFile().getPath();
ZoneInfoDB.TzData dataWithCorruptOverride =
- ZoneInfoDB.TzData.loadTzDataWithFallback(corruptFilePath, TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData.loadTzDataWithFallback(corruptFilePath, SYSTEM_TZDATA_FILE);
assertEquals(data.getVersion(), dataWithCorruptOverride.getVersion());
assertEquals(data.getAvailableIDs().length, dataWithCorruptOverride.getAvailableIDs().length);
}
@@ -64,7 +64,7 @@
// Given a valid override file, we should find ourselves using that.
public void testLoadTzDataWithFallback_goodOverrideFile() throws Exception {
- RandomAccessFile in = new RandomAccessFile(TZDATA_IN_ROOT, "r");
+ RandomAccessFile in = new RandomAccessFile(SYSTEM_TZDATA_FILE, "r");
byte[] content = new byte[(int) in.length()];
in.readFully(content);
in.close();
@@ -79,9 +79,9 @@
File goodFile = makeTemporaryFile(content);
try {
ZoneInfoDB.TzData dataWithOverride =
- ZoneInfoDB.TzData.loadTzDataWithFallback(goodFile.getPath(), TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData.loadTzDataWithFallback(goodFile.getPath(), SYSTEM_TZDATA_FILE);
assertEquals("9999z", dataWithOverride.getVersion());
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
assertEquals(data.getAvailableIDs().length, dataWithOverride.getAvailableIDs().length);
} finally {
goodFile.delete();
@@ -89,7 +89,7 @@
}
public void testLoadTzData_badHeader() throws Exception {
- RandomAccessFile in = new RandomAccessFile(TZDATA_IN_ROOT, "r");
+ RandomAccessFile in = new RandomAccessFile(SYSTEM_TZDATA_FILE, "r");
byte[] content = new byte[(int) in.length()];
in.readFully(content);
in.close();
@@ -212,7 +212,7 @@
// Confirms any caching that exists correctly handles TimeZone mutability.
public void testMakeTimeZone_timeZoneMutability() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
String tzId = "Europe/London";
ZoneInfo first = data.makeTimeZone(tzId);
ZoneInfo second = data.makeTimeZone(tzId);
@@ -229,21 +229,21 @@
}
public void testMakeTimeZone_notFound() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
assertNull(data.makeTimeZone("THIS_TZ_DOES_NOT_EXIST"));
assertFalse(data.hasTimeZone("THIS_TZ_DOES_NOT_EXIST"));
}
public void testMakeTimeZone_found() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
assertNotNull(data.makeTimeZone("Europe/London"));
assertTrue(data.hasTimeZone("Europe/London"));
}
public void testGetRulesVersion() throws Exception {
- ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(TZDATA_IN_ROOT);
+ ZoneInfoDB.TzData data = ZoneInfoDB.TzData.loadTzData(SYSTEM_TZDATA_FILE);
- String rulesVersion = ZoneInfoDB.TzData.getRulesVersion(new File(TZDATA_IN_ROOT));
+ String rulesVersion = ZoneInfoDB.TzData.getRulesVersion(new File(SYSTEM_TZDATA_FILE));
assertEquals(data.getVersion(), rulesVersion);
}
diff --git a/non_openjdk_java_files.mk b/non_openjdk_java_files.mk
index fd1353f..9d33f46 100644
--- a/non_openjdk_java_files.mk
+++ b/non_openjdk_java_files.mk
@@ -307,6 +307,8 @@
luni/src/main/java/libcore/util/Objects.java \
luni/src/main/java/libcore/util/RecoverySystem.java \
luni/src/main/java/libcore/util/SneakyThrow.java \
+ luni/src/main/java/libcore/util/TimeZoneDataFiles.java \
+ luni/src/main/java/libcore/util/TimeZoneFinder.java \
luni/src/main/java/libcore/util/ZoneInfo.java \
luni/src/main/java/libcore/util/ZoneInfoDB.java \
luni/src/main/java/libcore/util/HexEncoding.java \
diff --git a/ojluni/src/main/java/java/beans/ChangeListenerMap.java b/ojluni/src/main/java/java/beans/ChangeListenerMap.java
index fead0a4..fa8be47 100644
--- a/ojluni/src/main/java/java/beans/ChangeListenerMap.java
+++ b/ojluni/src/main/java/java/beans/ChangeListenerMap.java
@@ -76,7 +76,7 @@
*/
public final synchronized void add(String name, L listener) {
if (this.map == null) {
- this.map = new HashMap<String, L[]>();
+ this.map = new HashMap<>();
}
L[] array = this.map.get(name);
int size = (array != null)
@@ -146,7 +146,7 @@
public final void set(String name, L[] listeners) {
if (listeners != null) {
if (this.map == null) {
- this.map = new HashMap<String, L[]>();
+ this.map = new HashMap<>();
}
this.map.put(name, listeners);
}
@@ -167,7 +167,7 @@
if (this.map == null) {
return newArray(0);
}
- List<L> list = new ArrayList<L>();
+ List<L> list = new ArrayList<>();
L[] listeners = this.map.get(null);
if (listeners != null) {
diff --git a/ojluni/src/main/java/java/lang/System.java b/ojluni/src/main/java/java/lang/System.java
index 4797c1b..6e8f74f 100644
--- a/ojluni/src/main/java/java/lang/System.java
+++ b/ojluni/src/main/java/java/lang/System.java
@@ -41,6 +41,8 @@
import java.util.PropertyPermission;
import libcore.icu.ICU;
import libcore.io.Libcore;
+import libcore.util.TimeZoneDataFiles;
+
import sun.reflect.CallerSensitive;
import sun.security.util.SecurityConstants;
/**
@@ -996,7 +998,7 @@
// is prioritized over the properties in ICUConfig.properties. The issue with using
// that is that it doesn't play well with jarjar and it needs complicated build rules
// to change its default value.
- String icuDataPath = generateIcuDataPath();
+ String icuDataPath = TimeZoneDataFiles.generateIcuDataPath();
p.put("android.icu.impl.ICUBinary.dataPath", icuDataPath);
parsePropertyAssignments(p, specialProperties());
@@ -1023,37 +1025,6 @@
return p;
}
- private static String generateIcuDataPath() {
- StringBuilder icuDataPathBuilder = new StringBuilder();
- // ICU should first look in ANDROID_DATA. This is used for (optional) timezone data.
- String dataIcuDataPath = getEnvironmentPath("ANDROID_DATA", "/misc/zoneinfo/current/icu");
- if (dataIcuDataPath != null) {
- icuDataPathBuilder.append(dataIcuDataPath);
- }
-
- // ICU should always look in ANDROID_ROOT.
- String systemIcuDataPath = getEnvironmentPath("ANDROID_ROOT", "/usr/icu");
- if (systemIcuDataPath != null) {
- if (icuDataPathBuilder.length() > 0) {
- icuDataPathBuilder.append(":");
- }
- icuDataPathBuilder.append(systemIcuDataPath);
- }
- return icuDataPathBuilder.toString();
- }
-
- /**
- * Creates a path by combining the value of an environment variable with a relative path.
- * Returns {@code null} if the environment variable is not set.
- */
- private static String getEnvironmentPath(String environmentVariable, String path) {
- String variable = getenv(environmentVariable);
- if (variable == null) {
- return null;
- }
- return variable + path;
- }
-
private static Properties initProperties() {
Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableProps);
setDefaultChangeableProperties(p);
diff --git a/ojluni/src/main/java/java/lang/reflect/Array.java b/ojluni/src/main/java/java/lang/reflect/Array.java
index 8285be7..790a930 100644
--- a/ojluni/src/main/java/java/lang/reflect/Array.java
+++ b/ojluni/src/main/java/java/lang/reflect/Array.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, 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
@@ -57,6 +57,8 @@
* Array.newInstance(componentType, x);
* </pre>
* </blockquote>
+ * <p>The number of dimensions of the new array must not
+ * exceed 255.
*
* @param componentType the {@code Class} object representing the
* component type of the new array
@@ -64,7 +66,9 @@
* @return the new array
* @exception NullPointerException if the specified
* {@code componentType} parameter is null
- * @exception IllegalArgumentException if componentType is {@link Void#TYPE}
+ * @exception IllegalArgumentException if componentType is {@link
+ * Void#TYPE} or if the number of dimensions of the requested array
+ * instance exceed 255.
* @exception NegativeArraySizeException if the specified {@code length}
* is negative
*/
@@ -88,8 +92,7 @@
* {@code componentType}.
*
* <p>The number of dimensions of the new array must not
- * exceed the number of array dimensions supported by the
- * implementation (typically 255).
+ * exceed 255.
*
* @param componentType the {@code Class} object representing the component
* type of the new array
@@ -99,10 +102,9 @@
* @exception NullPointerException if the specified
* {@code componentType} argument is null
* @exception IllegalArgumentException if the specified {@code dimensions}
- * argument is a zero-dimensional array, or if the number of
- * requested dimensions exceeds the limit on the number of array dimensions
- * supported by the implementation (typically 255), or if componentType
- * is {@link Void#TYPE}.
+ * argument is a zero-dimensional array, if componentType is {@link
+ * Void#TYPE}, or if the number of dimensions of the requested array
+ * instance exceed 255.
* @exception NegativeArraySizeException if any of the components in
* the specified {@code dimensions} argument is negative.
*/
diff --git a/ojluni/src/main/java/java/lang/reflect/Constructor.java b/ojluni/src/main/java/java/lang/reflect/Constructor.java
index 3054e5f..de7176b 100644
--- a/ojluni/src/main/java/java/lang/reflect/Constructor.java
+++ b/ojluni/src/main/java/java/lang/reflect/Constructor.java
@@ -139,6 +139,7 @@
/**
* {@inheritDoc}
+ * @since 1.8
*/
public int getParameterCount() {
// Android-changed: This is handled by Executable.
diff --git a/ojluni/src/main/java/java/lang/reflect/Method.java b/ojluni/src/main/java/java/lang/reflect/Method.java
index 55d741c..ddda93d 100644
--- a/ojluni/src/main/java/java/lang/reflect/Method.java
+++ b/ojluni/src/main/java/java/lang/reflect/Method.java
@@ -186,6 +186,7 @@
/**
* {@inheritDoc}
+ * @since 1.8
*/
public int getParameterCount() {
// Android-changed: This is handled by Executable.
diff --git a/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java b/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
index b8e8e63..96a2d21 100644
--- a/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
+++ b/ojluni/src/main/java/java/lang/reflect/ReflectPermission.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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/java/net/DefaultDatagramSocketImplFactory.java b/ojluni/src/main/java/java/net/DefaultDatagramSocketImplFactory.java
index 18465ba..ad62c58 100644
--- a/ojluni/src/main/java/java/net/DefaultDatagramSocketImplFactory.java
+++ b/ojluni/src/main/java/java/net/DefaultDatagramSocketImplFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007,2011, 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
@@ -35,7 +35,7 @@
*/
class DefaultDatagramSocketImplFactory {
- static Class prefixImplClass = null;
+ static Class<?> prefixImplClass = null;
static {
String prefix = null;
@@ -53,10 +53,10 @@
}
/**
- * Creates a new {@code DatagramSocketImpl} instance.
+ * Creates a new <code>DatagramSocketImpl</code> instance.
*
* @param isMulticast true if this impl if for a MutlicastSocket
- * @return a new instance of a {@code DatagramSocketImpl}.
+ * @return a new instance of a <code>DatagramSocketImpl</code>.
*/
static DatagramSocketImpl createDatagramSocketImpl(boolean isMulticast /*unused on unix*/)
throws SocketException {
diff --git a/ojluni/src/main/java/java/net/DefaultInterface.java b/ojluni/src/main/java/java/net/DefaultInterface.java
index 9f4dfe1..0c31cb6 100644
--- a/ojluni/src/main/java/java/net/DefaultInterface.java
+++ b/ojluni/src/main/java/java/net/DefaultInterface.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2013, 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
@@ -26,7 +26,7 @@
package java.net;
/**
- * Choose a network inteface to be the default for
+ * Choose a network interface to be the default for
* outgoing IPv6 traffic that does not specify a scope_id (and which needs one).
*
* Platforms that do not require a default interface may return null
diff --git a/ojluni/src/main/java/java/net/PlainSocketImpl.java b/ojluni/src/main/java/java/net/PlainSocketImpl.java
index 7177098..f02006d 100644
--- a/ojluni/src/main/java/java/net/PlainSocketImpl.java
+++ b/ojluni/src/main/java/java/net/PlainSocketImpl.java
@@ -25,15 +25,34 @@
*/
package java.net;
+import android.system.ErrnoException;
+
import java.io.IOException;
import java.io.FileDescriptor;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
+import libcore.io.AsynchronousCloseMonitor;
+import libcore.io.IoBridge;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
import jdk.net.*;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.EAGAIN;
+import static android.system.OsConstants.EBADF;
+import static android.system.OsConstants.EINVAL;
+import static android.system.OsConstants.MSG_OOB;
+import static android.system.OsConstants.POLLERR;
+import static android.system.OsConstants.POLLIN;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.SHUT_RDWR;
import static sun.net.ExtendedOptionsImpl.*;
+// Android-changed: Rewritten to use android.system POSIX calls and assume AF_INET6.
/*
* On Unix systems we simply delegate to native methods.
*
@@ -91,29 +110,180 @@
}
}
- native void socketCreate(boolean isServer) throws IOException;
+ void socketCreate(boolean isStream) throws IOException {
+ // The fd object must not change after calling bind, because we rely on this undocumented
+ // behaviour. See libcore.java.net.SocketTest#testFileDescriptorStaysSame.
+ fd.setInt$(IoBridge.socket(AF_INET6, isStream ? SOCK_STREAM : SOCK_DGRAM, 0).getInt$());
- native void socketConnect(InetAddress address, int port, int timeout)
- throws IOException;
+ if (serverSocket != null) {
+ IoUtils.setBlocking(fd, false);
+ IoBridge.setSocketOption(fd, SO_REUSEADDR, true);
+ }
+ }
- native void socketBind(InetAddress address, int port)
- throws IOException;
+ void socketConnect(InetAddress address, int port, int timeout) throws IOException {
+ if (fd == null || !fd.valid()) {
+ throw new SocketException("Socket closed");
+ }
- native void socketListen(int count) throws IOException;
+ IoBridge.connect(fd, address, port, timeout);
- native void socketAccept(SocketImpl s) throws IOException;
+ this.address = address;
+ this.port = port;
- native int socketAvailable() throws IOException;
+ if (localport == 0) {
+ // If socket is pending close, fd becomes an AF_UNIX socket and calling
+ // getLocalInetSocketAddress will fail.
+ // http://b/34645743
+ if (!isClosedOrPending()) {
+ localport = IoBridge.getLocalInetSocketAddress(fd).getPort();
+ }
+ }
+ }
- native void socketClose0(boolean useDeferredClose) throws IOException;
+ void socketBind(InetAddress address, int port) throws IOException {
+ if (fd == null || !fd.valid()) {
+ throw new SocketException("Socket closed");
+ }
- native void socketShutdown(int howto) throws IOException;
+ IoBridge.bind(fd, address, port);
+
+ this.address = address;
+ if (port == 0) {
+ // Now that we're a connected socket, let's extract the port number that the system
+ // chose for us and store it in the Socket object.
+ localport = IoBridge.getLocalInetSocketAddress(fd).getPort();
+ } else {
+ localport = port;
+ }
+ }
+
+ void socketListen(int count) throws IOException {
+ if (fd == null || !fd.valid()) {
+ throw new SocketException("Socket closed");
+ }
+
+ try {
+ Libcore.os.listen(fd, count);
+ } catch (ErrnoException errnoException) {
+ throw errnoException.rethrowAsSocketException();
+ }
+ }
+
+ void socketAccept(SocketImpl s) throws IOException {
+ if (fd == null || !fd.valid()) {
+ throw new SocketException("Socket closed");
+ }
+
+ // poll() with a timeout of 0 means "poll for zero millis", but a Socket timeout == 0 means
+ // "wait forever". When timeout == 0 we pass -1 to poll.
+ if (timeout <= 0) {
+ IoBridge.poll(fd, POLLIN | POLLERR, -1);
+ } else {
+ IoBridge.poll(fd, POLLIN | POLLERR, timeout);
+ }
+
+ InetSocketAddress peerAddress = new InetSocketAddress();
+ try {
+ FileDescriptor newfd = Libcore.os.accept(fd, peerAddress);
+
+ s.fd.setInt$(newfd.getInt$());
+ s.address = peerAddress.getAddress();
+ s.port = peerAddress.getPort();
+ } catch (ErrnoException errnoException) {
+ if (errnoException.errno == EAGAIN) {
+ throw new SocketTimeoutException(errnoException);
+ } else if (errnoException.errno == EINVAL || errnoException.errno == EBADF) {
+ throw new SocketException("Socket closed");
+ }
+ errnoException.rethrowAsSocketException();
+ }
+
+ s.localport = IoBridge.getLocalInetSocketAddress(s.fd).getPort();
+ }
+
+ int socketAvailable() throws IOException {
+ return IoBridge.available(fd);
+ }
+
+ void socketClose0(boolean useDeferredClose) throws IOException {
+ if (fd == null || !fd.valid()) {
+ throw new SocketException("socket already closed");
+ }
+
+ FileDescriptor markerFD = null;
+ if (useDeferredClose) {
+ markerFD = getMarkerFD();
+ }
+
+ if (useDeferredClose && markerFD != null) {
+ try {
+ Libcore.os.dup2(markerFD, fd.getInt$());
+ Libcore.os.close(markerFD);
+
+ // This effectively closes the socket, needs to signal threads that blocks on this
+ // file descriptor.
+ AsynchronousCloseMonitor.signalBlockedThreads(fd);
+ } catch (ErrnoException errnoException) {
+ // close should not throw
+ }
+ } else {
+ // If requested or a markerFD cannot be created, a non-deferred close is performed
+ // instead.
+ IoBridge.closeAndSignalBlockedThreads(fd);
+ }
+ }
+
+ /*
+ * Create the marker file descriptor by establishing a loopback connection which we shutdown but
+ * do not close the fd. The result is an fd that can be used for read/write.
+ *
+ * The purpose is to keep hold of the raw fd handle until we are sure it is not used in any
+ * thread. Otherwise if we close the file descriptor directly, the system might reuse the raw fd
+ * number and threads holding old fd value might behave incorrectly.
+ */
+ private FileDescriptor getMarkerFD() throws SocketException {
+ FileDescriptor fd1 = new FileDescriptor();
+ FileDescriptor fd2 = new FileDescriptor();
+ try {
+ Libcore.os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd1, fd2);
+
+ // Shutdown fd1, any reads to this fd will get EOF; any writes will get an error.
+ Libcore.os.shutdown(fd1, SHUT_RDWR);
+ Libcore.os.close(fd2);
+ } catch (ErrnoException errnoException) {
+ // We might have reached the maximum file descriptor number and socketpair(2) would
+ // fail. In this case, return null and let caller to fall back to an alternative method
+ // that does not allocate more file descriptors.
+ return null;
+ }
+ return fd1;
+ }
+
+ void socketShutdown(int howto) throws IOException {
+ try {
+ Libcore.os.shutdown(fd, howto);
+ } catch (ErrnoException errnoException) {
+ throw errnoException.rethrowAsIOException();
+ }
+ }
native void socketSetOption0(int cmd, boolean on, Object value)
throws SocketException;
native int socketGetOption(int opt, Object iaContainerObj) throws SocketException;
- native void socketSendUrgentData(int data) throws IOException;
+ void socketSendUrgentData(int data) throws IOException {
+ if (fd == null || !fd.valid()) {
+ throw new SocketException("Socket closed");
+ }
+
+ try {
+ byte[] buffer = new byte[] { (byte) data };
+ Libcore.os.sendto(fd, buffer, 0, 1, MSG_OOB, null, 0);
+ } catch (ErrnoException errnoException) {
+ throw errnoException.rethrowAsSocketException();
+ }
+ }
}
diff --git a/ojluni/src/main/java/java/net/SocketInputStream.java b/ojluni/src/main/java/java/net/SocketInputStream.java
index 2757412..f5c4c8f 100644
--- a/ojluni/src/main/java/java/net/SocketInputStream.java
+++ b/ojluni/src/main/java/java/net/SocketInputStream.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2016, 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
@@ -153,11 +153,12 @@
}
// bounds check
- if (length <= 0 || off < 0 || off + length > b.length) {
+ if (length <= 0 || off < 0 || length > b.length - off) {
if (length == 0) {
return 0;
}
- throw new ArrayIndexOutOfBoundsException();
+ throw new ArrayIndexOutOfBoundsException("length == " + length
+ + " off == " + off + " buffer length == " + b.length);
}
boolean gotReset = false;
diff --git a/ojluni/src/main/java/java/net/SocketOutputStream.java b/ojluni/src/main/java/java/net/SocketOutputStream.java
index d37a7d7..c0173f0 100644
--- a/ojluni/src/main/java/java/net/SocketOutputStream.java
+++ b/ojluni/src/main/java/java/net/SocketOutputStream.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2016, 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
@@ -95,12 +95,12 @@
* @exception IOException If an I/O error has occurred.
*/
private void socketWrite(byte b[], int off, int len) throws IOException {
-
- if (len <= 0 || off < 0 || off + len > b.length) {
+ if (len <= 0 || off < 0 || len > b.length - off) {
if (len == 0) {
return;
}
- throw new ArrayIndexOutOfBoundsException();
+ throw new ArrayIndexOutOfBoundsException("len == " + len
+ + " off == " + off + " buffer length == " + b.length);
}
FileDescriptor fd = impl.acquireFD();
diff --git a/ojluni/src/main/java/java/net/URLStreamHandler.java b/ojluni/src/main/java/java/net/URLStreamHandler.java
index 1521b1b..eac8a78 100644
--- a/ojluni/src/main/java/java/net/URLStreamHandler.java
+++ b/ojluni/src/main/java/java/net/URLStreamHandler.java
@@ -168,9 +168,9 @@
(spec.charAt(start + 1) == '/')) {
start += 2;
i = spec.indexOf('/', start);
- if (i < 0) {
+ if (i < 0 || i > limit) {
i = spec.indexOf('?', start);
- if (i < 0)
+ if (i < 0 || i > limit)
i = limit;
}
@@ -178,8 +178,14 @@
int ind = authority.indexOf('@');
if (ind != -1) {
- userInfo = authority.substring(0, ind);
- host = authority.substring(ind+1);
+ if (ind != authority.lastIndexOf('@')) {
+ // more than one '@' in authority. This is not server based
+ userInfo = null;
+ host = null;
+ } else {
+ userInfo = authority.substring(0, ind);
+ host = authority.substring(ind+1);
+ }
} else {
userInfo = null;
}
diff --git a/ojluni/src/main/java/java/security/AccessControlContext.java b/ojluni/src/main/java/java/security/AccessControlContext.java
index 86a88f0..e95cff2 100644
--- a/ojluni/src/main/java/java/security/AccessControlContext.java
+++ b/ojluni/src/main/java/java/security/AccessControlContext.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2015, 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/java/security/AccessController.java b/ojluni/src/main/java/java/security/AccessController.java
index 1f7bdc5..b4d544c 100644
--- a/ojluni/src/main/java/java/security/AccessController.java
+++ b/ojluni/src/main/java/java/security/AccessController.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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/java/security/AuthProvider.java b/ojluni/src/main/java/java/security/AuthProvider.java
index 0308291..23ddb0a 100644
--- a/ojluni/src/main/java/java/security/AuthProvider.java
+++ b/ojluni/src/main/java/java/security/AuthProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2013, 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/java/security/CodeSource.java b/ojluni/src/main/java/java/security/CodeSource.java
index d678011..7396d91 100644
--- a/ojluni/src/main/java/java/security/CodeSource.java
+++ b/ojluni/src/main/java/java/security/CodeSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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/java/security/DomainCombiner.java b/ojluni/src/main/java/java/security/DomainCombiner.java
index 7dc0849..5426bf6 100644
--- a/ojluni/src/main/java/java/security/DomainCombiner.java
+++ b/ojluni/src/main/java/java/security/DomainCombiner.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2013, 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/java/security/NoSuchAlgorithmException.java b/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
index fb34981..951e44e 100644
--- a/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
+++ b/ojluni/src/main/java/java/security/NoSuchAlgorithmException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2003, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, 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
@@ -58,13 +58,13 @@
}
/**
- * Creates a <code>NoSuchAlgorithmException</code> with the specified
+ * Creates a {@code NoSuchAlgorithmException} with the specified
* detail message and cause.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A <tt>null</tt> value is permitted,
+ * {@link #getCause()} method). (A {@code null} value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @since 1.5
*/
@@ -73,13 +73,13 @@
}
/**
- * Creates a <code>NoSuchAlgorithmException</code> with the specified cause
- * and a detail message of <tt>(cause==null ? null : cause.toString())</tt>
+ * Creates a {@code NoSuchAlgorithmException} with the specified cause
+ * and a detail message of {@code (cause==null ? null : cause.toString())}
* (which typically contains the class and detail message of
- * <tt>cause</tt>).
+ * {@code cause}).
*
* @param cause the cause (which is saved for later retrieval by the
- * {@link #getCause()} method). (A <tt>null</tt> value is permitted,
+ * {@link #getCause()} method). (A {@code null} value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @since 1.5
*/
diff --git a/ojluni/src/main/java/java/security/Permission.java b/ojluni/src/main/java/java/security/Permission.java
index 9f04ac4..8d170c0 100644
--- a/ojluni/src/main/java/java/security/Permission.java
+++ b/ojluni/src/main/java/java/security/Permission.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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/java/security/Permissions.java b/ojluni/src/main/java/java/security/Permissions.java
index de5d451..7411e06 100644
--- a/ojluni/src/main/java/java/security/Permissions.java
+++ b/ojluni/src/main/java/java/security/Permissions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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/java/security/PrivilegedAction.java b/ojluni/src/main/java/java/security/PrivilegedAction.java
index ab39f98..bef7e44 100644
--- a/ojluni/src/main/java/java/security/PrivilegedAction.java
+++ b/ojluni/src/main/java/java/security/PrivilegedAction.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2013, 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/java/security/PrivilegedExceptionAction.java b/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
index 87e4209..bae883e 100644
--- a/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
+++ b/ojluni/src/main/java/java/security/PrivilegedExceptionAction.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2013, 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/java/security/SecurityPermission.java b/ojluni/src/main/java/java/security/SecurityPermission.java
index 432ee4b..7d2ec47 100644
--- a/ojluni/src/main/java/java/security/SecurityPermission.java
+++ b/ojluni/src/main/java/java/security/SecurityPermission.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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/java/security/UnresolvedPermission.java b/ojluni/src/main/java/java/security/UnresolvedPermission.java
index dec952b..6d97fbe 100644
--- a/ojluni/src/main/java/java/security/UnresolvedPermission.java
+++ b/ojluni/src/main/java/java/security/UnresolvedPermission.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, 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/java/text/DecimalFormatSymbols.java b/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
index 833b6b6..9e82738 100644
--- a/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
+++ b/ojluni/src/main/java/java/text/DecimalFormatSymbols.java
@@ -712,24 +712,34 @@
// Android-changed: maybeStripMarkers added in b/26207216, fixed in b/32465689.
/**
- * Attempts to strip RTL, LTR and Arabic letter markers from {@code symbol}. If the symbol's
- * length is 1, then the first char of the symbol is returned. If the symbol's length is more
- * than 1 and the first char is a marker, then this marker is ignored. In all other cases,
- * {@code fallback} is returned.
+ * Attempts to strip RTL, LTR and Arabic letter markers from {@code symbol}.
+ * If the string contains a single non-marker character (and any number of marker characters),
+ * then that character is returned, otherwise {@code fallback} is returned.
+ *
+ * @hide
*/
- private static char maybeStripMarkers(String symbol, char fallback) {
+ // VisibleForTesting
+ public static char maybeStripMarkers(String symbol, char fallback) {
final int length = symbol.length();
- if (length == 1) {
- return symbol.charAt(0);
- }
-
- if (length > 1) {
- char first = symbol.charAt(0);
- if (first =='\u200E' || first =='\u200F' || first =='\u061C') {
- return symbol.charAt(1);
+ if (length >= 1) {
+ boolean sawNonMarker = false;
+ char nonMarker = 0;
+ for (int i = 0; i < length; i++) {
+ final char c = symbol.charAt(i);
+ if (c == '\u200E' || c == '\u200F' || c == '\u061C') {
+ continue;
+ }
+ if (sawNonMarker) {
+ // More than one non-marker character.
+ return fallback;
+ }
+ sawNonMarker = true;
+ nonMarker = c;
+ }
+ if (sawNonMarker) {
+ return nonMarker;
}
}
-
return fallback;
}
diff --git a/ojluni/src/main/java/java/util/Hashtable.java b/ojluni/src/main/java/java/util/Hashtable.java
index a07569c..6061290 100644
--- a/ojluni/src/main/java/java/util/Hashtable.java
+++ b/ojluni/src/main/java/java/util/Hashtable.java
@@ -861,6 +861,12 @@
return h;
}
+ @Override
+ public synchronized V getOrDefault(Object key, V defaultValue) {
+ V result = get(key);
+ return (null == result) ? defaultValue : result;
+ }
+
@SuppressWarnings("unchecked")
@Override
public synchronized void forEach(BiConsumer<? super K, ? super V> action) {
@@ -902,57 +908,216 @@
}
}
- // BEGIN Android-changed: Just wrap synchronization around Map.super implementations.
- // Upstream uses different overridden implementations.
- @Override
- public synchronized V getOrDefault(Object key, V defaultValue) {
- return Map.super.getOrDefault(key, defaultValue);
- }
-
@Override
public synchronized V putIfAbsent(K key, V value) {
- return Map.super.putIfAbsent(key, value);
+ Objects.requireNonNull(value);
+
+ // Makes sure the key is not already in the hashtable.
+ HashtableEntry<?,?> tab[] = table;
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ HashtableEntry<K,V> entry = (HashtableEntry<K,V>)tab[index];
+ for (; entry != null; entry = entry.next) {
+ if ((entry.hash == hash) && entry.key.equals(key)) {
+ V old = entry.value;
+ if (old == null) {
+ entry.value = value;
+ }
+ return old;
+ }
+ }
+
+ addEntry(hash, key, value, index);
+ return null;
}
@Override
public synchronized boolean remove(Object key, Object value) {
- return Map.super.remove(key, value);
+ Objects.requireNonNull(value);
+
+ HashtableEntry<?,?> tab[] = table;
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+ for (HashtableEntry<K,V> prev = null; e != null; prev = e, e = e.next) {
+ if ((e.hash == hash) && e.key.equals(key) && e.value.equals(value)) {
+ modCount++;
+ if (prev != null) {
+ prev.next = e.next;
+ } else {
+ tab[index] = e.next;
+ }
+ count--;
+ e.value = null;
+ return true;
+ }
+ }
+ return false;
}
@Override
public synchronized boolean replace(K key, V oldValue, V newValue) {
- return Map.super.replace(key, oldValue, newValue);
+ Objects.requireNonNull(oldValue);
+ Objects.requireNonNull(newValue);
+ HashtableEntry<?,?> tab[] = table;
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+ for (; e != null; e = e.next) {
+ if ((e.hash == hash) && e.key.equals(key)) {
+ if (e.value.equals(oldValue)) {
+ e.value = newValue;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return false;
}
@Override
public synchronized V replace(K key, V value) {
- return Map.super.replace(key, value);
+ Objects.requireNonNull(value);
+ HashtableEntry<?,?> tab[] = table;
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+ for (; e != null; e = e.next) {
+ if ((e.hash == hash) && e.key.equals(key)) {
+ V oldValue = e.value;
+ e.value = value;
+ return oldValue;
+ }
+ }
+ return null;
}
@Override
- public synchronized V computeIfAbsent(K key, Function<? super K,
- ? extends V> mappingFunction) {
- return Map.super.computeIfAbsent(key, mappingFunction);
+ public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+ Objects.requireNonNull(mappingFunction);
+
+ HashtableEntry<?,?> tab[] = table;
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+ for (; e != null; e = e.next) {
+ if (e.hash == hash && e.key.equals(key)) {
+ // Hashtable not accept null value
+ return e.value;
+ }
+ }
+
+ V newValue = mappingFunction.apply(key);
+ if (newValue != null) {
+ addEntry(hash, key, newValue, index);
+ }
+
+ return newValue;
}
@Override
- public synchronized V computeIfPresent(K key, BiFunction<? super K,
- ? super V, ? extends V> remappingFunction) {
- return Map.super.computeIfPresent(key, remappingFunction);
+ public synchronized V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+
+ HashtableEntry<?,?> tab[] = table;
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+ for (HashtableEntry<K,V> prev = null; e != null; prev = e, e = e.next) {
+ if (e.hash == hash && e.key.equals(key)) {
+ V newValue = remappingFunction.apply(key, e.value);
+ if (newValue == null) {
+ modCount++;
+ if (prev != null) {
+ prev.next = e.next;
+ } else {
+ tab[index] = e.next;
+ }
+ count--;
+ } else {
+ e.value = newValue;
+ }
+ return newValue;
+ }
+ }
+ return null;
}
@Override
- public synchronized V compute(K key, BiFunction<? super K, ? super V,
- ? extends V> remappingFunction) {
- return Map.super.compute(key, remappingFunction);
+ public synchronized V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+
+ HashtableEntry<?,?> tab[] = table;
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+ for (HashtableEntry<K,V> prev = null; e != null; prev = e, e = e.next) {
+ if (e.hash == hash && Objects.equals(e.key, key)) {
+ V newValue = remappingFunction.apply(key, e.value);
+ if (newValue == null) {
+ modCount++;
+ if (prev != null) {
+ prev.next = e.next;
+ } else {
+ tab[index] = e.next;
+ }
+ count--;
+ } else {
+ e.value = newValue;
+ }
+ return newValue;
+ }
+ }
+
+ V newValue = remappingFunction.apply(key, null);
+ if (newValue != null) {
+ addEntry(hash, key, newValue, index);
+ }
+
+ return newValue;
}
@Override
- public synchronized V merge(K key, V value, BiFunction<? super V, ? super V,
- ? extends V> remappingFunction) {
- return Map.super.merge(key, value, remappingFunction);
+ public synchronized V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+
+ HashtableEntry<?,?> tab[] = table;
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+ @SuppressWarnings("unchecked")
+ HashtableEntry<K,V> e = (HashtableEntry<K,V>)tab[index];
+ for (HashtableEntry<K,V> prev = null; e != null; prev = e, e = e.next) {
+ if (e.hash == hash && e.key.equals(key)) {
+ V newValue = remappingFunction.apply(e.value, value);
+ if (newValue == null) {
+ modCount++;
+ if (prev != null) {
+ prev.next = e.next;
+ } else {
+ tab[index] = e.next;
+ }
+ count--;
+ } else {
+ e.value = newValue;
+ }
+ return newValue;
+ }
+ }
+
+ if (value != null) {
+ addEntry(hash, key, value, index);
+ }
+
+ return value;
}
- // END Android-changed: Just add synchronization around Map's default implementations.
/**
* Save the state of the Hashtable to a stream (i.e., serialize it).
diff --git a/ojluni/src/main/java/java/util/TimeZone.java b/ojluni/src/main/java/java/util/TimeZone.java
index c745291..28ad0ca 100644
--- a/ojluni/src/main/java/java/util/TimeZone.java
+++ b/ojluni/src/main/java/java/util/TimeZone.java
@@ -398,16 +398,8 @@
return result;
}
- // If we get here, it's because icu4c has nothing for us. Most commonly, this is in the
- // case of short names. For Pacific/Fiji, for example, icu4c has nothing better to offer
- // than "GMT+12:00". Why do we re-do this work ourselves? Because we have up-to-date
- // time zone transition data, which icu4c _doesn't_ use --- it uses its own baked-in copy,
- // which only gets updated when we update icu4c. http://b/7955614 and http://b/8026776.
-
- // TODO: should we generate these once, in TimeZoneNames.getDisplayName? Revisit when we
- // upgrade to icu4c 50 and rewrite the underlying native code. See also the
- // "element[j] != null" check in SimpleDateFormat.parseTimeZone, and the extra work in
- // DateFormatSymbols.getZoneStrings.
+ // We get here if this is a custom timezone or ICU doesn't have name data for the specific
+ // style and locale.
int offsetMillis = getRawOffset();
if (daylightTime) {
offsetMillis += getDSTSavings();
diff --git a/ojluni/src/main/java/java/util/regex/MatchResult.java b/ojluni/src/main/java/java/util/regex/MatchResult.java
index 9b52331..4f42eae 100644
--- a/ojluni/src/main/java/java/util/regex/MatchResult.java
+++ b/ojluni/src/main/java/java/util/regex/MatchResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2013, 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
@@ -77,9 +77,9 @@
public int start(int group);
/**
- * Returns the offset after the last character matched. </p>
+ * Returns the offset after the last character matched.
*
- * @return @return The offset after the last character matched
+ * @return The offset after the last character matched
*
* @throws IllegalStateException
* If no match has yet been attempted,
diff --git a/ojluni/src/main/java/java/util/regex/Matcher.java b/ojluni/src/main/java/java/util/regex/Matcher.java
index 3d79ba7..9af28aa 100644
--- a/ojluni/src/main/java/java/util/regex/Matcher.java
+++ b/ojluni/src/main/java/java/util/regex/Matcher.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1999, 2009, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2013, 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
@@ -444,7 +444,7 @@
*/
public boolean matches() {
synchronized (this) {
- matchFound = matchesImpl(address, input, matchOffsets);
+ matchFound = matchesImpl(address, matchOffsets);
}
return matchFound;
}
@@ -466,7 +466,7 @@
*/
public boolean find() {
synchronized (this) {
- matchFound = findNextImpl(address, input, matchOffsets);
+ matchFound = findNextImpl(address, matchOffsets);
}
return matchFound;
}
@@ -495,7 +495,7 @@
}
synchronized (this) {
- matchFound = findImpl(address, input, start, matchOffsets);
+ matchFound = findImpl(address, start, matchOffsets);
}
return matchFound;
}
@@ -516,7 +516,7 @@
*/
public boolean lookingAt() {
synchronized (this) {
- matchFound = lookingAtImpl(address, input, matchOffsets);
+ matchFound = lookingAtImpl(address, matchOffsets);
}
return matchFound;
}
@@ -1181,13 +1181,13 @@
}
private static native int getMatchedGroupIndex0(long patternAddr, String name);
- private static native boolean findImpl(long addr, String s, int startIndex, int[] offsets);
- private static native boolean findNextImpl(long addr, String s, int[] offsets);
+ private static native boolean findImpl(long addr, int startIndex, int[] offsets);
+ private static native boolean findNextImpl(long addr, int[] offsets);
private static native long getNativeFinalizer();
private static native int groupCountImpl(long addr);
private static native boolean hitEndImpl(long addr);
- private static native boolean lookingAtImpl(long addr, String s, int[] offsets);
- private static native boolean matchesImpl(long addr, String s, int[] offsets);
+ private static native boolean lookingAtImpl(long addr, int[] offsets);
+ private static native boolean matchesImpl(long addr, int[] offsets);
private static native int nativeSize();
private static native long openImpl(long patternAddr);
private static native boolean requireEndImpl(long addr);
diff --git a/ojluni/src/main/java/javax/crypto/CipherOutputStream.java b/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
index 6b8d273..370f7af 100644
--- a/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
+++ b/ojluni/src/main/java/javax/crypto/CipherOutputStream.java
@@ -210,6 +210,8 @@
obuffer = cipher.doFinal();
} catch (IllegalBlockSizeException | BadPaddingException e) {
obuffer = null;
+ // Android-added: Throw an exception when the underlying cipher does. http://b/36636576
+ throw new IOException(e);
}
try {
flush();
diff --git a/ojluni/src/main/java/sun/nio/fs/LinuxFileSystemProvider.java b/ojluni/src/main/java/sun/nio/fs/LinuxFileSystemProvider.java
index 8f1fa47..e6468d1 100644
--- a/ojluni/src/main/java/sun/nio/fs/LinuxFileSystemProvider.java
+++ b/ojluni/src/main/java/sun/nio/fs/LinuxFileSystemProvider.java
@@ -46,7 +46,11 @@
@Override
LinuxFileStore getFileStore(UnixPath path) throws IOException {
- return new LinuxFileStore(path);
+ // Android-changed: Complete information about file systems is neither available to regular
+ // apps nor the system server due to SELinux policies.
+ // return new LinuxFileStore(path);
+
+ throw new SecurityException("getFileStore");
}
@Override
@@ -55,14 +59,18 @@
Class<V> type,
LinkOption... options)
{
- if (type == DosFileAttributeView.class) {
- return (V) new LinuxDosFileAttributeView(UnixPath.toUnixPath(obj),
- Util.followLinks(options));
- }
- if (type == UserDefinedFileAttributeView.class) {
- return (V) new LinuxUserDefinedFileAttributeView(UnixPath.toUnixPath(obj),
- Util.followLinks(options));
- }
+ // Android-changed: Delegate to UnixFileSystemProvider, remove support for "dos" and
+ // "user" file attribute views which are not supported.
+ //
+ // if (type == DosFileAttributeView.class) {
+ // return (V) new LinuxDosFileAttributeView(UnixPath.toUnixPath(obj),
+ // Util.followLinks(options));
+ // }
+ // if (type == UserDefinedFileAttributeView.class) {
+ // return (V) new LinuxUserDefinedFileAttributeView(UnixPath.toUnixPath(obj),
+ // Util.followLinks(options));
+ // }
+
return super.getFileAttributeView(obj, type, options);
}
@@ -71,14 +79,18 @@
String name,
LinkOption... options)
{
- if (name.equals("dos")) {
- return new LinuxDosFileAttributeView(UnixPath.toUnixPath(obj),
- Util.followLinks(options));
- }
- if (name.equals("user")) {
- return new LinuxUserDefinedFileAttributeView(UnixPath.toUnixPath(obj),
- Util.followLinks(options));
- }
+ // Android-changed: Delegate to UnixFileSystemProvider, remove support for "dos" and
+ // "user" file attribute views which are not supported.
+ //
+ // if (name.equals("dos")) {
+ // return new LinuxDosFileAttributeView(UnixPath.toUnixPath(obj),
+ // Util.followLinks(options));
+ // }
+ // if (name.equals("user")) {
+ // return new LinuxUserDefinedFileAttributeView(UnixPath.toUnixPath(obj),
+ // Util.followLinks(options));
+ // }
+
return super.getFileAttributeView(obj, name, options);
}
@@ -89,13 +101,17 @@
LinkOption... options)
throws IOException
{
- if (type == DosFileAttributes.class) {
- DosFileAttributeView view =
- getFileAttributeView(file, DosFileAttributeView.class, options);
- return (A) view.readAttributes();
- } else {
- return super.readAttributes(file, type, options);
- }
+ // Android-changed: Delegate to UnixFileSystemProvider, remove support for "dos" and
+ // "user" file attribute views which are not supported.
+ //
+ // if (type == DosFileAttributes.class) {
+ // DosFileAttributeView view =
+ // getFileAttributeView(file, DosFileAttributeView.class, options);
+ // return (A) view.readAttributes();
+ // } else {
+ // return super.readAttributes(file, type, options);
+ // }
+ return super.readAttributes(file, type, options);
}
@Override
diff --git a/ojluni/src/main/java/sun/nio/fs/UnixFileSystemProvider.java b/ojluni/src/main/java/sun/nio/fs/UnixFileSystemProvider.java
index 182a57b..3724956 100644
--- a/ojluni/src/main/java/sun/nio/fs/UnixFileSystemProvider.java
+++ b/ojluni/src/main/java/sun/nio/fs/UnixFileSystemProvider.java
@@ -359,13 +359,17 @@
@Override
public FileStore getFileStore(Path obj) throws IOException {
- UnixPath file = UnixPath.toUnixPath(obj);
- SecurityManager sm = System.getSecurityManager();
- if (sm != null) {
- sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
- file.checkRead();
- }
- return getFileStore(file);
+ // Android-changed: Complete information about file systems is neither available to regular
+ // apps nor the system server due to SELinux policies.
+ //
+ // UnixPath file = UnixPath.toUnixPath(obj);
+ // SecurityManager sm = System.getSecurityManager();
+ // if (sm != null) {
+ // sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
+ // file.checkRead();
+ // }
+ // return getFileStore(file);
+ throw new SecurityException("getFileStore");
}
@Override
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/AdaptableX509CertSelector.java b/ojluni/src/main/java/sun/security/provider/certpath/AdaptableX509CertSelector.java
index db36c0e..0ad5387 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/AdaptableX509CertSelector.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/AdaptableX509CertSelector.java
@@ -36,9 +36,7 @@
import sun.security.util.Debug;
import sun.security.util.DerInputStream;
-import sun.security.util.DerOutputStream;
import sun.security.x509.SerialNumber;
-import sun.security.x509.KeyIdentifier;
import sun.security.x509.AuthorityKeyIdentifierExtension;
/**
@@ -131,13 +129,7 @@
serial = null;
if (ext != null) {
- KeyIdentifier akid = (KeyIdentifier)ext.get(
- AuthorityKeyIdentifierExtension.KEY_ID);
- if (akid != null) {
- DerOutputStream derout = new DerOutputStream();
- derout.putOctetString(akid.getIdentifier());
- ski = derout.toByteArray();
- }
+ ski = ext.getEncodedKeyIdentifier();
SerialNumber asn = (SerialNumber)ext.get(
AuthorityKeyIdentifierExtension.SERIAL_NUMBER);
if (asn != null) {
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/AlgorithmChecker.java b/ojluni/src/main/java/sun/security/provider/certpath/AlgorithmChecker.java
index ab75ff0..e92590a 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/AlgorithmChecker.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/AlgorithmChecker.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2016, 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
@@ -31,12 +31,10 @@
import java.util.Collections;
import java.util.Set;
import java.util.EnumSet;
-import java.util.HashSet;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.KeyFactory;
import java.security.AlgorithmParameters;
-import java.security.NoSuchAlgorithmException;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.security.cert.X509CRL;
@@ -48,10 +46,13 @@
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.PKIXReason;
-import java.io.IOException;
-import java.security.interfaces.*;
-import java.security.spec.*;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import sun.security.util.AnchorCertificates;
+import sun.security.util.CertConstraintParameters;
+import sun.security.util.Debug;
import sun.security.util.DisabledAlgorithmConstraints;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CRLImpl;
@@ -69,18 +70,34 @@
* @see PKIXParameters
*/
final public class AlgorithmChecker extends PKIXCertPathChecker {
+ private static final Debug debug = Debug.getInstance("certpath");
private final AlgorithmConstraints constraints;
private final PublicKey trustedPubKey;
private PublicKey prevPubKey;
private final static Set<CryptoPrimitive> SIGNATURE_PRIMITIVE_SET =
- EnumSet.of(CryptoPrimitive.SIGNATURE);
+ Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
+
+ private final static Set<CryptoPrimitive> KU_PRIMITIVE_SET =
+ Collections.unmodifiableSet(EnumSet.of(
+ CryptoPrimitive.SIGNATURE,
+ CryptoPrimitive.KEY_ENCAPSULATION,
+ CryptoPrimitive.PUBLIC_KEY_ENCRYPTION,
+ CryptoPrimitive.KEY_AGREEMENT));
private final static DisabledAlgorithmConstraints
certPathDefaultConstraints = new DisabledAlgorithmConstraints(
DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
+ // If there is no "cacerts" keyword, then disable anchor checking
+ private static final boolean publicCALimits =
+ certPathDefaultConstraints.checkProperty("jdkCA");
+
+ // If anchor checking enabled, this will be true if the trust anchor
+ // has a match in the cacerts file
+ private boolean trustedMatch = false;
+
/**
* Create a new <code>AlgorithmChecker</code> with the algorithm
* constraints specified in security property
@@ -129,6 +146,11 @@
if (anchor.getTrustedCert() != null) {
this.trustedPubKey = anchor.getTrustedCert().getPublicKey();
+ // Check for anchor certificate restrictions
+ trustedMatch = checkFingerprint(anchor.getTrustedCert());
+ if (trustedMatch && debug != null) {
+ debug.println("trustedMatch = true");
+ }
} else {
this.trustedPubKey = anchor.getCAPublicKey();
}
@@ -137,6 +159,19 @@
this.constraints = constraints;
}
+ // Check this 'cert' for restrictions in the AnchorCertificates
+ // trusted certificates list
+ private static boolean checkFingerprint(X509Certificate cert) {
+ if (!publicCALimits) {
+ return false;
+ }
+
+ if (debug != null) {
+ debug.println("AlgorithmChecker.contains: " + cert.getSigAlgName());
+ }
+ return AnchorCertificates.contains(cert);
+ }
+
@Override
public void init(boolean forward) throws CertPathValidatorException {
// Note that this class does not support forward mode.
@@ -174,45 +209,19 @@
return;
}
- X509CertImpl x509Cert = null;
- try {
- x509Cert = X509CertImpl.toImpl((X509Certificate)cert);
- } catch (CertificateException ce) {
- throw new CertPathValidatorException(ce);
- }
-
- PublicKey currPubKey = x509Cert.getPublicKey();
- String currSigAlg = x509Cert.getSigAlgName();
-
- AlgorithmId algorithmId = null;
- try {
- algorithmId = (AlgorithmId)x509Cert.get(X509CertImpl.SIG_ALG);
- } catch (CertificateException ce) {
- throw new CertPathValidatorException(ce);
- }
-
- AlgorithmParameters currSigAlgParams = algorithmId.getParameters();
-
- // Check the current signature algorithm
- if (!constraints.permits(
- SIGNATURE_PRIMITIVE_SET,
- currSigAlg, currSigAlgParams)) {
- throw new CertPathValidatorException(
- "Algorithm constraints check failed: " + currSigAlg,
- null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
- }
-
// check the key usage and key size
- boolean[] keyUsage = x509Cert.getKeyUsage();
+ boolean[] keyUsage = ((X509Certificate) cert).getKeyUsage();
if (keyUsage != null && keyUsage.length < 9) {
throw new CertPathValidatorException(
"incorrect KeyUsage extension",
null, null, -1, PKIXReason.INVALID_KEY_USAGE);
}
+ // Assume all key usage bits are set if key usage is not present
+ Set<CryptoPrimitive> primitives = KU_PRIMITIVE_SET;
+
if (keyUsage != null) {
- Set<CryptoPrimitive> primitives =
- EnumSet.noneOf(CryptoPrimitive.class);
+ primitives = EnumSet.noneOf(CryptoPrimitive.class);
if (keyUsage[0] || keyUsage[1] || keyUsage[5] || keyUsage[6]) {
// keyUsage[0]: KeyUsage.digitalSignature
@@ -237,26 +246,70 @@
// KeyUsage.encipherOnly and KeyUsage.decipherOnly are
// undefined in the absence of the keyAgreement bit.
- if (!primitives.isEmpty()) {
- if (!constraints.permits(primitives, currPubKey)) {
- throw new CertPathValidatorException(
- "algorithm constraints check failed",
- null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
- }
+ if (primitives.isEmpty()) {
+ throw new CertPathValidatorException(
+ "incorrect KeyUsage extension bits",
+ null, null, -1, PKIXReason.INVALID_KEY_USAGE);
}
}
+ PublicKey currPubKey = cert.getPublicKey();
+
+ // Check against DisabledAlgorithmConstraints certpath constraints.
+ // permits() will throw exception on failure.
+ certPathDefaultConstraints.permits(primitives,
+ new CertConstraintParameters((X509Certificate)cert,
+ trustedMatch));
+ // new CertConstraintParameters(x509Cert, trustedMatch));
+ // If there is no previous key, set one and exit
+ if (prevPubKey == null) {
+ prevPubKey = currPubKey;
+ return;
+ }
+
+ X509CertImpl x509Cert;
+ AlgorithmId algorithmId;
+ try {
+ x509Cert = X509CertImpl.toImpl((X509Certificate)cert);
+ algorithmId = (AlgorithmId)x509Cert.get(X509CertImpl.SIG_ALG);
+ } catch (CertificateException ce) {
+ throw new CertPathValidatorException(ce);
+ }
+
+ AlgorithmParameters currSigAlgParams = algorithmId.getParameters();
+ String currSigAlg = x509Cert.getSigAlgName();
+
+ // If 'constraints' is not of DisabledAlgorithmConstraints, check all
+ // everything individually
+ if (!(constraints instanceof DisabledAlgorithmConstraints)) {
+ // Check the current signature algorithm
+ if (!constraints.permits(
+ SIGNATURE_PRIMITIVE_SET,
+ currSigAlg, currSigAlgParams)) {
+ throw new CertPathValidatorException(
+ "Algorithm constraints check failed on signature " +
+ "algorithm: " + currSigAlg, null, null, -1,
+ BasicReason.ALGORITHM_CONSTRAINED);
+ }
+
+ if (!constraints.permits(primitives, currPubKey)) {
+ throw new CertPathValidatorException(
+ "Algorithm constraints check failed on keysize: " +
+ sun.security.util.KeyUtil.getKeySize(currPubKey),
+ null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
+ }
+ }
+
// Check with previous cert for signature algorithm and public key
if (prevPubKey != null) {
- if (currSigAlg != null) {
if (!constraints.permits(
SIGNATURE_PRIMITIVE_SET,
currSigAlg, prevPubKey, currSigAlgParams)) {
throw new CertPathValidatorException(
- "Algorithm constraints check failed: " + currSigAlg,
+ "Algorithm constraints check failed on " +
+ "signature algorithm: " + currSigAlg,
null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
}
- }
// Inherit key parameters from previous key
if (PKIX.isDSAPublicKeyWithoutParams(currPubKey)) {
@@ -269,7 +322,7 @@
DSAParams params = ((DSAPublicKey)prevPubKey).getParams();
if (params == null) {
throw new CertPathValidatorException(
- "Key parameters missing");
+ "Key parameters missing from public key.");
}
try {
@@ -317,6 +370,11 @@
// Don't bother to change the trustedPubKey.
if (anchor.getTrustedCert() != null) {
prevPubKey = anchor.getTrustedCert().getPublicKey();
+ // Check for anchor certificate restrictions
+ trustedMatch = checkFingerprint(anchor.getTrustedCert());
+ if (trustedMatch && debug != null) {
+ debug.println("trustedMatch = true");
+ }
} else {
prevPubKey = anchor.getCAPublicKey();
}
@@ -357,10 +415,10 @@
if (!certPathDefaultConstraints.permits(
SIGNATURE_PRIMITIVE_SET, sigAlgName, key, sigAlgParams)) {
throw new CertPathValidatorException(
- "algorithm check failed: " + sigAlgName + " is disabled",
+ "Algorithm constraints check failed on signature algorithm: " +
+ sigAlgName + " is disabled",
null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
}
}
}
-
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/DistributionPointFetcher.java b/ojluni/src/main/java/sun/security/provider/certpath/DistributionPointFetcher.java
index 5b9dd60..5d9e657 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/DistributionPointFetcher.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/DistributionPointFetcher.java
@@ -33,7 +33,6 @@
import java.util.*;
import sun.security.util.Debug;
-import sun.security.util.DerOutputStream;
import static sun.security.x509.PKIXExtensions.*;
import sun.security.x509.*;
@@ -608,12 +607,9 @@
AuthorityKeyIdentifierExtension akidext =
crlImpl.getAuthKeyIdExtension();
if (akidext != null) {
- KeyIdentifier akid = (KeyIdentifier)akidext.get(
- AuthorityKeyIdentifierExtension.KEY_ID);
- if (akid != null) {
- DerOutputStream derout = new DerOutputStream();
- derout.putOctetString(akid.getIdentifier());
- certSel.setSubjectKeyIdentifier(derout.toByteArray());
+ byte[] kid = akidext.getEncodedKeyIdentifier();
+ if (kid != null) {
+ certSel.setSubjectKeyIdentifier(kid);
}
SerialNumber asn = (SerialNumber)akidext.get(
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/ForwardBuilder.java b/ojluni/src/main/java/sun/security/provider/certpath/ForwardBuilder.java
index d708ebf..57e819c 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/ForwardBuilder.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/ForwardBuilder.java
@@ -46,9 +46,10 @@
import sun.security.util.Debug;
import sun.security.x509.AccessDescription;
import sun.security.x509.AuthorityInfoAccessExtension;
+import sun.security.x509.AuthorityKeyIdentifierExtension;
import static sun.security.x509.PKIXExtensions.*;
import sun.security.x509.X500Name;
-import sun.security.x509.AuthorityKeyIdentifierExtension;
+import sun.security.x509.X509CertImpl;
/**
* This class represents a forward builder, which is able to retrieve
@@ -69,7 +70,6 @@
private AdaptableX509CertSelector caSelector;
private X509CertSelector caTargetSelector;
TrustAnchor trustAnchor;
- private Comparator<X509Certificate> comparator;
private boolean searchAllCertStores = true;
/**
@@ -93,7 +93,6 @@
trustedSubjectDNs.add(anchor.getCA());
}
}
- comparator = new PKIXCertComparator(trustedSubjectDNs);
this.searchAllCertStores = searchAllCertStores;
}
@@ -122,6 +121,8 @@
* As each cert is added, it is sorted based on the PKIXCertComparator
* algorithm.
*/
+ Comparator<X509Certificate> comparator =
+ new PKIXCertComparator(trustedSubjectDNs, currState.cert);
Set<X509Certificate> certs = new TreeSet<>(comparator);
/*
@@ -265,14 +266,6 @@
(caSelector, currentState.subjectNamesTraversed);
/*
- * Facilitate certification path construction with authority
- * key identifier and subject key identifier.
- */
- AuthorityKeyIdentifierExtension akidext =
- currentState.cert.getAuthorityKeyIdentifierExtension();
- caSelector.setSkiAndSerialNumber(akidext);
-
- /*
* check the validity period
*/
caSelector.setValidityPeriod(currentState.cert.getNotBefore(),
@@ -404,41 +397,68 @@
*
* Preference order for current cert:
*
- * 1) Issuer matches a trusted subject
+ * 1) The key identifier of an AKID extension (if present) in the
+ * previous certificate matches the key identifier in the SKID extension
+ *
+ * 2) Issuer matches a trusted subject
* Issuer: ou=D,ou=C,o=B,c=A
*
- * 2) Issuer is a descendant of a trusted subject (in order of
+ * 3) Issuer is a descendant of a trusted subject (in order of
* number of links to the trusted subject)
* a) Issuer: ou=E,ou=D,ou=C,o=B,c=A [links=1]
* b) Issuer: ou=F,ou=E,ou=D,ou=C,ou=B,c=A [links=2]
*
- * 3) Issuer is an ancestor of a trusted subject (in order of number of
+ * 4) Issuer is an ancestor of a trusted subject (in order of number of
* links to the trusted subject)
* a) Issuer: ou=C,o=B,c=A [links=1]
* b) Issuer: o=B,c=A [links=2]
*
- * 4) Issuer is in the same namespace as a trusted subject (in order of
+ * 5) Issuer is in the same namespace as a trusted subject (in order of
* number of links to the trusted subject)
* a) Issuer: ou=G,ou=C,o=B,c=A [links=2]
* b) Issuer: ou=H,o=B,c=A [links=3]
*
- * 5) Issuer is an ancestor of certificate subject (in order of number
+ * 6) Issuer is an ancestor of certificate subject (in order of number
* of links to the certificate subject)
* a) Issuer: ou=K,o=J,c=A
* Subject: ou=L,ou=K,o=J,c=A
* b) Issuer: o=J,c=A
* Subject: ou=L,ou=K,0=J,c=A
*
- * 6) Any other certificates
+ * 7) Any other certificates
*/
static class PKIXCertComparator implements Comparator<X509Certificate> {
- final static String METHOD_NME = "PKIXCertComparator.compare()";
+ static final String METHOD_NME = "PKIXCertComparator.compare()";
private final Set<X500Principal> trustedSubjectDNs;
+ private final X509CertSelector certSkidSelector;
- PKIXCertComparator(Set<X500Principal> trustedSubjectDNs) {
+ PKIXCertComparator(Set<X500Principal> trustedSubjectDNs,
+ X509CertImpl previousCert) throws IOException {
this.trustedSubjectDNs = trustedSubjectDNs;
+ this.certSkidSelector = getSelector(previousCert);
+ }
+
+ /**
+ * Returns an X509CertSelector for matching on the authority key
+ * identifier, or null if not applicable.
+ */
+ private X509CertSelector getSelector(X509CertImpl previousCert)
+ throws IOException {
+ if (previousCert != null) {
+ AuthorityKeyIdentifierExtension akidExt =
+ previousCert.getAuthorityKeyIdentifierExtension();
+ if (akidExt != null) {
+ byte[] skid = akidExt.getEncodedKeyIdentifier();
+ if (skid != null) {
+ X509CertSelector selector = new X509CertSelector();
+ selector.setSubjectKeyIdentifier(skid);
+ return selector;
+ }
+ }
+ }
+ return null;
}
/**
@@ -462,6 +482,16 @@
// if certs are the same, return 0
if (oCert1.equals(oCert2)) return 0;
+ // If akid/skid match then it is preferable
+ if (certSkidSelector != null) {
+ if (certSkidSelector.match(oCert1)) {
+ return -1;
+ }
+ if (certSkidSelector.match(oCert2)) {
+ return 1;
+ }
+ }
+
X500Principal cIssuer1 = oCert1.getIssuerX500Principal();
X500Principal cIssuer2 = oCert2.getIssuerX500Principal();
X500Name cIssuer1Name = X500Name.asX500Name(cIssuer1);
diff --git a/ojluni/src/main/java/sun/security/provider/certpath/PKIXMasterCertPathValidator.java b/ojluni/src/main/java/sun/security/provider/certpath/PKIXMasterCertPathValidator.java
index cfffba8..e85ef2d 100644
--- a/ojluni/src/main/java/sun/security/provider/certpath/PKIXMasterCertPathValidator.java
+++ b/ojluni/src/main/java/sun/security/provider/certpath/PKIXMasterCertPathValidator.java
@@ -131,8 +131,8 @@
} catch (CertPathValidatorException cpve) {
throw new CertPathValidatorException(cpve.getMessage(),
- cpve.getCause(), cpOriginal, cpSize - (i + 1),
- cpve.getReason());
+ (cpve.getCause() != null) ? cpve.getCause() : cpve,
+ cpOriginal, cpSize - (i + 1), cpve.getReason());
}
}
diff --git a/ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java b/ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java
index 4c3efd9..1b64d9d 100644
--- a/ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java
+++ b/ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016 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
@@ -29,7 +29,6 @@
import java.security.AlgorithmConstraints;
import java.security.PrivilegedAction;
import java.security.Security;
-import java.util.Map;
import java.util.Set;
/**
@@ -45,8 +44,7 @@
}
// Get algorithm constraints from the specified security property.
- private static void loadAlgorithmsMap(Map<String, String[]> algorithmsMap,
- String propertyName) {
+ static String[] getAlgorithms(String propertyName) {
String property = AccessController.doPrivileged(
(PrivilegedAction<String>) () -> Security.getProperty(
propertyName));
@@ -69,18 +67,7 @@
if (algorithmsInProperty == null) {
algorithmsInProperty = new String[0];
}
- algorithmsMap.put(propertyName, algorithmsInProperty);
- }
-
- static String[] getAlgorithms(Map<String, String[]> algorithmsMap,
- String propertyName) {
- synchronized (algorithmsMap) {
- if (!algorithmsMap.containsKey(propertyName)) {
- loadAlgorithmsMap(algorithmsMap, propertyName);
- }
-
- return algorithmsMap.get(propertyName);
- }
+ return algorithmsInProperty;
}
static boolean checkAlgorithm(String[] algorithms, String algorithm,
diff --git a/ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java b/ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java
index 394b846..dae529a 100644
--- a/ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java
+++ b/ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, 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
@@ -38,19 +38,7 @@
private static final Pattern pattern =
Pattern.compile("with|and", Pattern.CASE_INSENSITIVE);
- /**
- * Decompose the standard algorithm name into sub-elements.
- * <p>
- * For example, we need to decompose "SHA1WithRSA" into "SHA1" and "RSA"
- * so that we can check the "SHA1" and "RSA" algorithm constraints
- * separately.
- * <p>
- * Please override the method if need to support more name pattern.
- */
- public Set<String> decompose(String algorithm) {
- if (algorithm == null || algorithm.length() == 0) {
- return new HashSet<>();
- }
+ private static Set<String> decomposeImpl(String algorithm) {
// algorithm/mode/padding
String[] transTockens = transPattern.split(algorithm);
@@ -76,6 +64,24 @@
elements.add(token);
}
}
+ return elements;
+ }
+
+ /**
+ * Decompose the standard algorithm name into sub-elements.
+ * <p>
+ * For example, we need to decompose "SHA1WithRSA" into "SHA1" and "RSA"
+ * so that we can check the "SHA1" and "RSA" algorithm constraints
+ * separately.
+ * <p>
+ * Please override the method if need to support more name pattern.
+ */
+ public Set<String> decompose(String algorithm) {
+ if (algorithm == null || algorithm.length() == 0) {
+ return new HashSet<>();
+ }
+
+ Set<String> elements = decomposeImpl(algorithm);
// In Java standard algorithm name specification, for different
// purpose, the SHA-1 and SHA-2 algorithm names are different. For
@@ -127,4 +133,40 @@
return elements;
}
+ private static void hasLoop(Set<String> elements, String find, String replace) {
+ if (elements.contains(find)) {
+ if (!elements.contains(replace)) {
+ elements.add(replace);
+}
+ elements.remove(find);
+ }
+ }
+
+ /*
+ * This decomposes a standard name into sub-elements with a consistent
+ * message digest algorithm name to avoid overly complicated checking.
+ */
+ public static Set<String> decomposeOneHash(String algorithm) {
+ if (algorithm == null || algorithm.length() == 0) {
+ return new HashSet<>();
+ }
+
+ Set<String> elements = decomposeImpl(algorithm);
+
+ hasLoop(elements, "SHA-1", "SHA1");
+ hasLoop(elements, "SHA-224", "SHA224");
+ hasLoop(elements, "SHA-256", "SHA256");
+ hasLoop(elements, "SHA-384", "SHA384");
+ hasLoop(elements, "SHA-512", "SHA512");
+
+ return elements;
+ }
+
+ /*
+ * The provided message digest algorithm name will return a consistent
+ * naming scheme.
+ */
+ public static String hashName(String algorithm) {
+ return algorithm.replace("-", "");
+ }
}
diff --git a/ojluni/src/main/java/sun/security/util/AnchorCertificates.java b/ojluni/src/main/java/sun/security/util/AnchorCertificates.java
new file mode 100644
index 0000000..6bc0030
--- /dev/null
+++ b/ojluni/src/main/java/sun/security/util/AnchorCertificates.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.AccessController;
+import java.security.KeyStore;
+import java.security.PrivilegedAction;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.HashSet;
+
+import sun.security.x509.X509CertImpl;
+
+/**
+ * The purpose of this class is to determine the trust anchor certificates is in
+ * the cacerts file. This is used for PKIX CertPath checking.
+ */
+public class AnchorCertificates {
+
+ private static final Debug debug = Debug.getInstance("certpath");
+ private static final String HASH = "SHA-256";
+ private static HashSet<String> certs;
+
+ static {
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+ File f = new File(System.getProperty("java.home"),
+ "lib/security/cacerts");
+ KeyStore cacerts;
+ try {
+ cacerts = KeyStore.getInstance("JKS");
+ try (FileInputStream fis = new FileInputStream(f)) {
+ cacerts.load(fis, "changeit".toCharArray());
+ certs = new HashSet<>();
+ Enumeration<String> list = cacerts.aliases();
+ String alias;
+ while (list.hasMoreElements()) {
+ alias = list.nextElement();
+ // Check if this cert is labeled a trust anchor.
+ if (alias.contains(" [jdk")) {
+ X509Certificate cert = (X509Certificate) cacerts
+ .getCertificate(alias);
+ certs.add(X509CertImpl.getFingerprint(HASH, cert));
+ }
+ }
+ }
+ } catch (Exception e) {
+ if (debug != null) {
+ debug.println("Error parsing cacerts");
+ }
+ e.printStackTrace();
+ }
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Checks if a certificate is a trust anchor.
+ *
+ * @param cert the certificate to check
+ * @return true if the certificate is trusted.
+ */
+ public static boolean contains(X509Certificate cert) {
+ String key = X509CertImpl.getFingerprint(HASH, cert);
+ boolean result = certs.contains(key);
+ if (result && debug != null) {
+ debug.println("AnchorCertificate.contains: matched " +
+ cert.getSubjectDN());
+ }
+ return result;
+ }
+
+ private AnchorCertificates() {}
+}
diff --git a/ojluni/src/main/java/sun/security/util/CertConstraintParameters.java b/ojluni/src/main/java/sun/security/util/CertConstraintParameters.java
new file mode 100644
index 0000000..9f7a938
--- /dev/null
+++ b/ojluni/src/main/java/sun/security/util/CertConstraintParameters.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2016, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.util;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * This class is a wrapper for keeping state and passing objects between PKIX,
+ * AlgorithmChecker, and DisabledAlgorithmConstraints.
+ */
+public class CertConstraintParameters {
+ // A certificate being passed to check against constraints.
+ private final X509Certificate cert;
+
+ // This is true if the trust anchor in the certificate chain matches a cert
+ // in AnchorCertificates
+ private final boolean trustedMatch;
+
+ public CertConstraintParameters(X509Certificate c, boolean match) {
+ cert = c;
+ trustedMatch = match;
+ }
+
+ public CertConstraintParameters(X509Certificate c) {
+ this(c, false);
+ }
+
+ // Returns if the trust anchor has a match if anchor checking is enabled.
+ public boolean isTrustedMatch() {
+ return trustedMatch;
+ }
+
+ public X509Certificate getCertificate() {
+ return cert;
+ }
+}
diff --git a/ojluni/src/main/java/sun/security/util/DisabledAlgorithmConstraints.java b/ojluni/src/main/java/sun/security/util/DisabledAlgorithmConstraints.java
index 05da9ba..d8c1462 100644
--- a/ojluni/src/main/java/sun/security/util/DisabledAlgorithmConstraints.java
+++ b/ojluni/src/main/java/sun/security/util/DisabledAlgorithmConstraints.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2015, 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
@@ -28,12 +28,14 @@
import java.security.CryptoPrimitive;
import java.security.AlgorithmParameters;
import java.security.Key;
-import java.util.Locale;
-import java.util.Set;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertPathValidatorException.BasicReason;
+import java.security.cert.X509Certificate;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
@@ -44,6 +46,7 @@
* for the syntax of the disabled algorithm string.
*/
public class DisabledAlgorithmConstraints extends AbstractAlgorithmConstraints {
+ private static final Debug debug = Debug.getInstance("certpath");
// the known security property, jdk.certpath.disabledAlgorithms
public final static String PROPERTY_CERTPATH_DISABLED_ALGS =
@@ -53,13 +56,9 @@
public final static String PROPERTY_TLS_DISABLED_ALGS =
"jdk.tls.disabledAlgorithms";
- private final static Map<String, String[]> disabledAlgorithmsMap =
- new HashMap<>();
- private final static Map<String, KeySizeConstraints> keySizeConstraintsMap =
- new HashMap<>();
private final String[] disabledAlgorithms;
- private final KeySizeConstraints keySizeConstraints;
+ private final Constraints algorithmConstraints;
/**
* Initialize algorithm constraints with the specified security property.
@@ -71,14 +70,25 @@
this(propertyName, new AlgorithmDecomposer());
}
+ /**
+ * Initialize algorithm constraints with the specified security property
+ * for a specific usage type.
+ *
+ * @param propertyName the security property name that define the disabled
+ * algorithm constraints
+ * @param decomposer an alternate AlgorithmDecomposer.
+ */
public DisabledAlgorithmConstraints(String propertyName,
AlgorithmDecomposer decomposer) {
super(decomposer);
- disabledAlgorithms = getAlgorithms(disabledAlgorithmsMap, propertyName);
- keySizeConstraints = getKeySizeConstraints(disabledAlgorithms,
- propertyName);
+ disabledAlgorithms = getAlgorithms(propertyName);
+ algorithmConstraints = new Constraints(disabledAlgorithms);
}
+ /*
+ * This only checks if the algorithm has been completely disabled. If
+ * there are keysize or other limit, this method allow the algorithm.
+ */
@Override
final public boolean permits(Set<CryptoPrimitive> primitives,
String algorithm, AlgorithmParameters parameters) {
@@ -90,11 +100,19 @@
return checkAlgorithm(disabledAlgorithms, algorithm, decomposer);
}
+ /*
+ * Checks if the key algorithm has been disabled or constraints have been
+ * placed on the key.
+ */
@Override
final public boolean permits(Set<CryptoPrimitive> primitives, Key key) {
return checkConstraints(primitives, "", key, null);
}
+ /*
+ * Checks if the key algorithm has been disabled or if constraints have
+ * been placed on the key.
+ */
@Override
final public boolean permits(Set<CryptoPrimitive> primitives,
String algorithm, Key key, AlgorithmParameters parameters) {
@@ -106,7 +124,39 @@
return checkConstraints(primitives, algorithm, key, parameters);
}
- // Check algorithm constraints
+ /*
+ * Check if a x509Certificate object is permitted. Check if all
+ * algorithms are allowed, certificate constraints, and the
+ * public key against key constraints.
+ *
+ * Uses new style permit() which throws exceptions.
+ */
+ public final void permits(Set<CryptoPrimitive> primitives,
+ CertConstraintParameters cp) throws CertPathValidatorException {
+ checkConstraints(primitives, cp);
+ }
+
+ /*
+ * Check if Certificate object is within the constraints.
+ * Uses new style permit() which throws exceptions.
+ */
+ public final void permits(Set<CryptoPrimitive> primitives,
+ X509Certificate cert) throws CertPathValidatorException {
+ checkConstraints(primitives, new CertConstraintParameters(cert));
+ }
+
+ // Check if a string is contained inside the property
+ public boolean checkProperty(String param) {
+ param = param.toLowerCase(Locale.ENGLISH);
+ for (String block : disabledAlgorithms) {
+ if (block.toLowerCase(Locale.ENGLISH).indexOf(param) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Check algorithm constraints with key and algorithm
private boolean checkConstraints(Set<CryptoPrimitive> primitives,
String algorithm, Key key, AlgorithmParameters parameters) {
@@ -115,7 +165,7 @@
throw new IllegalArgumentException("The key cannot be null");
}
- // check the target algorithm
+ // check the signature algorithm
if (algorithm != null && algorithm.length() != 0) {
if (!permits(primitives, algorithm, parameters)) {
return false;
@@ -128,97 +178,203 @@
}
// check the key constraints
- if (keySizeConstraints.disables(key)) {
- return false;
- }
-
- return true;
+ return algorithmConstraints.permits(key);
}
- private static KeySizeConstraints getKeySizeConstraints(
- String[] disabledAlgorithms, String propertyName) {
- synchronized (keySizeConstraintsMap) {
- if(!keySizeConstraintsMap.containsKey(propertyName)) {
- // map the key constraints
- KeySizeConstraints keySizeConstraints =
- new KeySizeConstraints(disabledAlgorithms);
- keySizeConstraintsMap.put(propertyName, keySizeConstraints);
- }
+ /*
+ * Check algorithm constraints with Certificate
+ * Uses new style permit() which throws exceptions.
+ */
+ private void checkConstraints(Set<CryptoPrimitive> primitives,
+ CertConstraintParameters cp) throws CertPathValidatorException {
- return keySizeConstraintsMap.get(propertyName);
+ X509Certificate cert = cp.getCertificate();
+ String algorithm = cert.getSigAlgName();
+
+ // Check signature algorithm is not disabled
+ if (!permits(primitives, algorithm, null)) {
+ throw new CertPathValidatorException(
+ "Algorithm constraints check failed on disabled "+
+ "signature algorithm: " + algorithm,
+ null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
}
+
+ // Check key algorithm is not disabled
+ if (!permits(primitives, cert.getPublicKey().getAlgorithm(), null)) {
+ throw new CertPathValidatorException(
+ "Algorithm constraints check failed on disabled "+
+ "public key algorithm: " + algorithm,
+ null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
+ }
+
+ // Check the certificate and key constraints
+ algorithmConstraints.permits(cp);
+
}
/**
- * key constraints
+ * Key and Certificate Constraints
+ *
+ * The complete disabling of an algorithm is not handled by Constraints or
+ * Constraint classes. That is addressed with
+ * permit(Set<CryptoPrimitive>, String, AlgorithmParameters)
+ *
+ * When passing a Key to permit(), the boolean return values follow the
+ * same as the interface class AlgorithmConstraints.permit(). This is to
+ * maintain compatibility:
+ * 'true' means the operation is allowed.
+ * 'false' means it failed the constraints and is disallowed.
+ *
+ * When passing CertConstraintParameters through permit(), an exception
+ * will be thrown on a failure to better identify why the operation was
+ * disallowed.
*/
- private static class KeySizeConstraints {
- private static final Pattern pattern = Pattern.compile(
- "(\\S+)\\s+keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)");
- private Map<String, Set<KeySizeConstraint>> constraintsMap =
- Collections.synchronizedMap(
- new HashMap<String, Set<KeySizeConstraint>>());
+ private static class Constraints {
+ private Map<String, Set<Constraint>> constraintsMap = new HashMap<>();
+ private static final Pattern keySizePattern = Pattern.compile(
+ "keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)");
- public KeySizeConstraints(String[] restrictions) {
- for (String restriction : restrictions) {
- if (restriction == null || restriction.isEmpty()) {
+ public Constraints(String[] constraintArray) {
+ for (String constraintEntry : constraintArray) {
+ if (constraintEntry == null || constraintEntry.isEmpty()) {
continue;
}
- Matcher matcher = pattern.matcher(restriction);
- if (matcher.matches()) {
- String algorithm = matcher.group(1);
+ constraintEntry = constraintEntry.trim();
+ if (debug != null) {
+ debug.println("Constraints: " + constraintEntry);
+ }
- KeySizeConstraint.Operator operator =
- KeySizeConstraint.Operator.of(matcher.group(2));
- int length = Integer.parseInt(matcher.group(3));
+ // Check if constraint is a complete disabling of an
+ // algorithm or has conditions.
+ String algorithm;
+ String policy;
+ int space = constraintEntry.indexOf(' ');
+ if (space > 0) {
+ algorithm = AlgorithmDecomposer.hashName(
+ constraintEntry.substring(0, space).
+ toUpperCase(Locale.ENGLISH));
+ policy = constraintEntry.substring(space + 1);
+ } else {
+ constraintsMap.computeIfAbsent(
+ constraintEntry.toUpperCase(Locale.ENGLISH),
+ k -> new HashSet<>());
+ continue;
+ }
- algorithm = algorithm.toLowerCase(Locale.ENGLISH);
+ // Convert constraint conditions into Constraint classes
+ Constraint c, lastConstraint = null;
+ // Allow only one jdkCA entry per constraint entry
+ boolean jdkCALimit = false;
- synchronized (constraintsMap) {
- if (!constraintsMap.containsKey(algorithm)) {
- constraintsMap.put(algorithm,
- new HashSet<KeySizeConstraint>());
+ for (String entry : policy.split("&")) {
+ entry = entry.trim();
+
+ Matcher matcher = keySizePattern.matcher(entry);
+ if (matcher.matches()) {
+ if (debug != null) {
+ debug.println("Constraints set to keySize: " +
+ entry);
}
+ c = new KeySizeConstraint(algorithm,
+ KeySizeConstraint.Operator.of(matcher.group(1)),
+ Integer.parseInt(matcher.group(2)));
- Set<KeySizeConstraint> constraintSet =
- constraintsMap.get(algorithm);
- KeySizeConstraint constraint =
- new KeySizeConstraint(operator, length);
- constraintSet.add(constraint);
+ } else if (entry.equalsIgnoreCase("jdkCA")) {
+ if (debug != null) {
+ debug.println("Constraints set to jdkCA.");
+ }
+ if (jdkCALimit) {
+ throw new IllegalArgumentException("Only one " +
+ "jdkCA entry allowed in property. " +
+ "Constraint: " + constraintEntry);
+ }
+ c = new jdkCAConstraint(algorithm);
+ jdkCALimit = true;
+ } else {
+ throw new IllegalArgumentException("Error in security" +
+ " property. Constraint unknown: " + entry);
}
+
+ // Link multiple conditions for a single constraint
+ // into a linked list.
+ if (lastConstraint == null) {
+ if (!constraintsMap.containsKey(algorithm)) {
+ constraintsMap.putIfAbsent(algorithm,
+ new HashSet<>());
+ }
+ constraintsMap.get(algorithm).add(c);
+ } else {
+ lastConstraint.nextConstraint = c;
+ }
+ lastConstraint = c;
}
}
}
- // Does this KeySizeConstraints disable the specified key?
- public boolean disables(Key key) {
- String algorithm = key.getAlgorithm().toLowerCase(Locale.ENGLISH);
- synchronized (constraintsMap) {
- if (constraintsMap.containsKey(algorithm)) {
- Set<KeySizeConstraint> constraintSet =
- constraintsMap.get(algorithm);
- for (KeySizeConstraint constraint : constraintSet) {
- if (constraint.disables(key)) {
- return true;
- }
- }
- }
- }
+ // Get applicable constraints based off the signature algorithm
+ private Set<Constraint> getConstraints(String algorithm) {
+ return constraintsMap.get(algorithm);
+ }
+ // Check if KeySizeConstraints permit the specified key
+ public boolean permits(Key key) {
+ Set<Constraint> set = getConstraints(key.getAlgorithm());
+ if (set == null) {
+ return true;
+ }
+ for (Constraint constraint : set) {
+ if (!constraint.permits(key)) {
+ if (debug != null) {
+ debug.println("keySizeConstraint: failed key " +
+ "constraint check " + KeyUtil.getKeySize(key));
+ }
return false;
}
+ }
+ return true;
}
- /**
- * Key size constraint.
- *
- * e.g. "keysize <= 1024"
- */
- private static class KeySizeConstraint {
+ // Check if constraints permit this cert.
+ public void permits(CertConstraintParameters cp)
+ throws CertPathValidatorException {
+ X509Certificate cert = cp.getCertificate();
+
+ if (debug != null) {
+ debug.println("Constraints.permits(): " + cert.getSigAlgName());
+ }
+
+ // Get all signature algorithms to check for constraints
+ Set<String> algorithms =
+ AlgorithmDecomposer.decomposeOneHash(cert.getSigAlgName());
+ if (algorithms == null || algorithms.isEmpty()) {
+ return;
+ }
+
+ // Attempt to add the public key algorithm to the set
+ algorithms.add(cert.getPublicKey().getAlgorithm());
+
+ // Check all applicable constraints
+ for (String algorithm : algorithms) {
+ Set<Constraint> set = getConstraints(algorithm);
+ if (set == null) {
+ continue;
+ }
+ for (Constraint constraint : set) {
+ constraint.permits(cp);
+ }
+ }
+ }
+ }
+
+ // Abstract class for algorithm constraint checking
+ private abstract static class Constraint {
+ String algorithm;
+ Constraint nextConstraint = null;
+
// operator
- static enum Operator {
+ enum Operator {
EQ, // "=="
NE, // "!="
LT, // "<"
@@ -242,16 +398,77 @@
return GE;
}
- throw new IllegalArgumentException(
- s + " is not a legal Operator");
+ throw new IllegalArgumentException("Error in security " +
+ "property. " + s + " is not a legal Operator");
}
}
+ /**
+ * Check if an algorithm constraint permit this key to be used.
+ * @param key Public key
+ * @return true if constraints do not match
+ */
+ public boolean permits(Key key) {
+ return true;
+ }
+
+ /**
+ * Check if an algorithm constraint is permit this certificate to
+ * be used.
+ * @param cp CertificateParameter containing certificate and state info
+ * @return true if constraints do not match
+ */
+ public abstract void permits(CertConstraintParameters cp)
+ throws CertPathValidatorException;
+ }
+
+ /*
+ * This class contains constraints dealing with the certificate chain
+ * of the certificate.
+ */
+ private static class jdkCAConstraint extends Constraint {
+ jdkCAConstraint(String algo) {
+ algorithm = algo;
+ }
+
+ /*
+ * Check if each constraint fails and check if there is a linked
+ * constraint Any permitted constraint will exit the linked list
+ * to allow the operation.
+ */
+ public void permits(CertConstraintParameters cp)
+ throws CertPathValidatorException {
+ if (debug != null) {
+ debug.println("jdkCAConstraints.permits(): " + algorithm);
+ }
+
+ // Return false if the chain has a trust anchor in cacerts
+ if (cp.isTrustedMatch()) {
+ if (nextConstraint != null) {
+ nextConstraint.permits(cp);
+ return;
+ }
+ throw new CertPathValidatorException(
+ "Algorithm constraints check failed on certificate " +
+ "anchor limits",
+ null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
+ }
+ }
+ }
+
+
+ /*
+ * This class contains constraints dealing with the key size
+ * support limits per algorithm. e.g. "keySize <= 1024"
+ */
+ private static class KeySizeConstraint extends Constraint {
+
private int minSize; // the minimal available key size
private int maxSize; // the maximal available key size
private int prohibitedSize = -1; // unavailable key sizes
- public KeySizeConstraint(Operator operator, int length) {
+ public KeySizeConstraint(String algo, Operator operator, int length) {
+ algorithm = algo;
switch (operator) {
case EQ: // an unavailable key size
this.minSize = 0;
@@ -285,21 +502,58 @@
}
}
- // Does this key constraint disable the specified key?
- public boolean disables(Key key) {
- int size = KeyUtil.getKeySize(key);
+ /*
+ * If we are passed a certificate, extract the public key and use it.
+ *
+ * Check if each constraint fails and check if there is a linked
+ * constraint Any permitted constraint will exit the linked list
+ * to allow the operation.
+ */
+ public void permits(CertConstraintParameters cp)
+ throws CertPathValidatorException {
+ if (!permitsImpl(cp.getCertificate().getPublicKey())) {
+ if (nextConstraint != null) {
+ nextConstraint.permits(cp);
+ return;
+ }
+ throw new CertPathValidatorException(
+ "Algorithm constraints check failed on keysize limits",
+ null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
+ }
+ }
+
+ // Check if key constraint disable the specified key
+ // Uses old style permit()
+ public boolean permits(Key key) {
+ // If we recursively find a constraint that permits us to use
+ // this key, return true and skip any other constraint checks.
+ if (nextConstraint != null && nextConstraint.permits(key)) {
+ return true;
+ }
+ if (debug != null) {
+ debug.println("KeySizeConstraints.permits(): " + algorithm);
+ }
+
+ return permitsImpl(key);
+ }
+
+ private boolean permitsImpl(Key key) {
+ // Verify this constraint is for this public key algorithm
+ if (algorithm.compareToIgnoreCase(key.getAlgorithm()) != 0) {
+ return true;
+ }
+
+ int size = KeyUtil.getKeySize(key);
if (size == 0) {
- return true; // we don't allow any key of size 0.
+ return false; // we don't allow any key of size 0.
} else if (size > 0) {
- return ((size < minSize) || (size > maxSize) ||
+ return !((size < minSize) || (size > maxSize) ||
(prohibitedSize == size));
} // Otherwise, the key size is not accessible. Conservatively,
// please don't disable such keys.
- return false;
+ return true;
+ }
}
}
-
-}
-
diff --git a/ojluni/src/main/java/sun/security/x509/AuthorityKeyIdentifierExtension.java b/ojluni/src/main/java/sun/security/x509/AuthorityKeyIdentifierExtension.java
index afd7c99..9e76a3a 100644
--- a/ojluni/src/main/java/sun/security/x509/AuthorityKeyIdentifierExtension.java
+++ b/ojluni/src/main/java/sun/security/x509/AuthorityKeyIdentifierExtension.java
@@ -307,4 +307,16 @@
public String getName() {
return (NAME);
}
+
+ /**
+ * Return the encoded key identifier, or null if not specified.
+ */
+ public byte[] getEncodedKeyIdentifier() throws IOException {
+ if (id != null) {
+ DerOutputStream derOut = new DerOutputStream();
+ id.encode(derOut);
+ return derOut.toByteArray();
+ }
+ return null;
+ }
}
diff --git a/ojluni/src/main/java/sun/security/x509/X509CertImpl.java b/ojluni/src/main/java/sun/security/x509/X509CertImpl.java
index f9db2c5..de4d313 100644
--- a/ojluni/src/main/java/sun/security/x509/X509CertImpl.java
+++ b/ojluni/src/main/java/sun/security/x509/X509CertImpl.java
@@ -1970,7 +1970,7 @@
// BEGIN Android-removed
// public String getFingerprint(String algorithm) {
// return fingerprints.computeIfAbsent(algorithm,
-// x -> getCertificateFingerPrint(x));
+// x -> getFingerprint(x, this));
// }
// END Android-removed
@@ -1978,11 +1978,12 @@
* Gets the requested finger print of the certificate. The result
* only contains 0-9 and A-F. No small case, no colon.
*/
- private String getCertificateFingerPrint(String mdAlg) {
+ public static String getFingerprint(String algorithm,
+ X509Certificate cert) {
String fingerPrint = "";
try {
- byte[] encCertInfo = getEncoded();
- MessageDigest md = MessageDigest.getInstance(mdAlg);
+ byte[] encCertInfo = cert.getEncoded();
+ MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] digest = md.digest(encCertInfo);
StringBuffer buf = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
diff --git a/ojluni/src/main/native/PlainSocketImpl.c b/ojluni/src/main/native/PlainSocketImpl.c
index e2ba1dc..c4b5be7 100644
--- a/ojluni/src/main/native/PlainSocketImpl.c
+++ b/ojluni/src/main/native/PlainSocketImpl.c
@@ -90,33 +90,6 @@
}
/*
- * Create the marker file descriptor by establishing a loopback connection
- * which we shutdown but do not close the fd. The result is an fd that
- * can be used for read/write.
- */
-static int getMarkerFD()
-{
- int sv[2];
-
-#ifdef AF_UNIX
- if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
- return -1;
- }
-#else
- return -1;
-#endif
-
- /*
- * Finally shutdown sv[0] (any reads to this fd will get
- * EOF; any writes will get an error).
- */
- JVM_SocketShutdown(sv[0], 2);
- JVM_SocketClose(sv[1]);
-
- return sv[0];
-}
-
-/*
* Return the file descriptor given a PlainSocketImpl
*/
static int getFD(JNIEnv *env, jobject this) {
@@ -162,724 +135,6 @@
/*
* Class: java_net_PlainSocketImpl
- * Method: socketCreate
- * Signature: (Z)V */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketCreate(JNIEnv *env, jobject this,
- jboolean stream) {
- jobject fdObj, ssObj;
- int fd;
- int type = (stream ? SOCK_STREAM : SOCK_DGRAM);
-#ifdef AF_INET6
- int domain = ipv6_available() ? AF_INET6 : AF_INET;
-#else
- int domain = AF_INET;
-#endif
-
- if (socketExceptionCls == NULL) {
- jclass c = (*env)->FindClass(env, "java/net/SocketException");
- CHECK_NULL(c);
- socketExceptionCls = (jclass)(*env)->NewGlobalRef(env, c);
- CHECK_NULL(socketExceptionCls);
- }
- fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-
- if (fdObj == NULL) {
- (*env)->ThrowNew(env, socketExceptionCls, "null fd object");
- return;
- }
-
- if ((fd = JVM_Socket(domain, type, 0)) == JVM_IO_ERR) {
- /* note: if you run out of fds, you may not be able to load
- * the exception class, and get a NoClassDefFoundError
- * instead.
- */
- NET_ThrowNew(env, errno, "can't create socket");
- return;
- }
- tagSocket(env, fd);
-
-#ifdef AF_INET6
- /* Disable IPV6_V6ONLY to ensure dual-socket support */
- if (domain == AF_INET6) {
- int arg = 0;
- if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg,
- sizeof(int)) < 0) {
- NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6");
- untagSocket(env, fd);
- close(fd);
- return;
- }
- }
-#endif /* AF_INET6 */
-
- /*
- * If this is a server socket then enable SO_REUSEADDR
- * automatically and set to non blocking.
- */
- ssObj = (*env)->GetObjectField(env, this, psi_serverSocketID);
- if (ssObj != NULL) {
- int arg = 1;
- SET_NONBLOCKING(fd);
- if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg,
- sizeof(arg)) < 0) {
- NET_ThrowNew(env, errno, "cannot set SO_REUSEADDR");
- untagSocket(env, fd);
- close(fd);
- return;
- }
- }
-
- (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
-}
-
-/*
- * inetAddress is the address object passed to the socket connect
- * call.
- *
- * Class: java_net_PlainSocketImpl
- * Method: socketConnect
- * Signature: (Ljava/net/InetAddress;I)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketConnect(JNIEnv *env, jobject this,
- jobject iaObj, jint port,
- jint timeout)
-{
- jint localport = (*env)->GetIntField(env, this, psi_localportID);
- int len = 0;
-
- /* fdObj is the FileDescriptor field on this */
- jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-
- jclass clazz = (*env)->GetObjectClass(env, this);
-
- jobject fdLock;
-
- jint trafficClass = (*env)->GetIntField(env, this, psi_trafficClassID);
-
- /* fd is an int field on iaObj */
- jint fd;
-
- SOCKADDR him;
- /* The result of the connection */
- int connect_rv = -1;
-
- if (IS_NULL(fdObj)) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
- return;
- } else {
- fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
- }
- if (IS_NULL(iaObj)) {
- JNU_ThrowNullPointerException(env, "inet address argument null.");
- return;
- }
-
- /* connect */
- if (NET_InetAddressToSockaddr(env, iaObj, port, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) {
- return;
- }
- setDefaultScopeID(env, (struct sockaddr *)&him);
-
-#ifdef AF_INET6
- if (trafficClass != 0 && ipv6_available()) {
- NET_SetTrafficClass((struct sockaddr *)&him, trafficClass);
- }
-#endif /* AF_INET6 */
- if (timeout <= 0) {
- connect_rv = NET_Connect(fd, (struct sockaddr *)&him, len);
-#ifdef __solaris__
- if (connect_rv == JVM_IO_ERR && errno == EINPROGRESS ) {
-
- /* This can happen if a blocking connect is interrupted by a signal.
- * See 6343810.
- */
- while (1) {
-#ifndef USE_SELECT
- {
- struct pollfd pfd;
- pfd.fd = fd;
- pfd.events = POLLOUT;
-
- connect_rv = NET_Poll(&pfd, 1, -1);
- }
-#else
- {
- fd_set wr, ex;
-
- FD_ZERO(&wr);
- FD_SET(fd, &wr);
- FD_ZERO(&ex);
- FD_SET(fd, &ex);
-
- connect_rv = NET_Select(fd+1, 0, &wr, &ex, 0);
- }
-#endif
-
- if (connect_rv == JVM_IO_ERR) {
- if (errno == EINTR) {
- continue;
- } else {
- break;
- }
- }
- if (connect_rv > 0) {
- int optlen;
- /* has connection been established */
- optlen = sizeof(connect_rv);
- if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR,
- (void*)&connect_rv, &optlen) <0) {
- connect_rv = errno;
- }
-
- if (connect_rv != 0) {
- /* restore errno */
- errno = connect_rv;
- connect_rv = JVM_IO_ERR;
- }
- break;
- }
- }
- }
-#endif
- } else {
- /*
- * A timeout was specified. We put the socket into non-blocking
- * mode, connect, and then wait for the connection to be
- * established, fail, or timeout.
- */
- SET_NONBLOCKING(fd);
-
- /* no need to use NET_Connect as non-blocking */
- connect_rv = connect(fd, (struct sockaddr *)&him, len);
-
- /* connection not established immediately */
- if (connect_rv != 0) {
- int optlen;
- jlong prevTime = JVM_CurrentTimeMillis(env, 0);
-
- if (errno != EINPROGRESS) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
- "connect failed");
- SET_BLOCKING(fd);
- return;
- }
-
- /*
- * Wait for the connection to be established or a
- * timeout occurs. poll/select needs to handle EINTR in
- * case lwp sig handler redirects any process signals to
- * this thread.
- */
- while (1) {
- jlong newTime;
-#ifndef USE_SELECT
- {
- struct pollfd pfd;
- pfd.fd = fd;
- pfd.events = POLLOUT;
-
- errno = 0;
- connect_rv = NET_Poll(&pfd, 1, timeout);
- }
-#else
- {
- fd_set wr, ex;
- struct timeval t;
-
- t.tv_sec = timeout / 1000;
- t.tv_usec = (timeout % 1000) * 1000;
-
- FD_ZERO(&wr);
- FD_SET(fd, &wr);
- FD_ZERO(&ex);
- FD_SET(fd, &ex);
-
- errno = 0;
- connect_rv = NET_Select(fd+1, 0, &wr, &ex, &t);
- }
-#endif
-
- if (connect_rv >= 0) {
- break;
- }
- if (errno != EINTR) {
- break;
- }
-
- /*
- * The poll was interrupted so adjust timeout and
- * restart
- */
- newTime = JVM_CurrentTimeMillis(env, 0);
- timeout -= (newTime - prevTime);
- if (timeout <= 0) {
- connect_rv = 0;
- break;
- }
- prevTime = newTime;
-
- } /* while */
-
- if (connect_rv == 0) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
- "connect timed out");
-
- /*
- * Timeout out but connection may still be established.
- * At the high level it should be closed immediately but
- * just in case we make the socket blocking again and
- * shutdown input & output.
- */
- SET_BLOCKING(fd);
- JVM_SocketShutdown(fd, 2);
- return;
- }
-
- /* has connection been established */
- optlen = sizeof(connect_rv);
- if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, (void*)&connect_rv,
- &optlen) <0) {
- connect_rv = errno;
- }
- }
-
- /* make socket blocking again */
- SET_BLOCKING(fd);
-
- /* restore errno */
- if (connect_rv != 0) {
- errno = connect_rv;
- connect_rv = JVM_IO_ERR;
- }
- }
-
- /* report the appropriate exception */
- if (connect_rv < 0) {
-
-#ifdef __linux__
- /*
- * Linux/GNU distribution setup /etc/hosts so that
- * InetAddress.getLocalHost gets back the loopback address
- * rather than the host address. Thus a socket can be
- * bound to the loopback address and the connect will
- * fail with EADDRNOTAVAIL. In addition the Linux kernel
- * returns the wrong error in this case - it returns EINVAL
- * instead of EADDRNOTAVAIL. We handle this here so that
- * a more descriptive exception text is used.
- */
- if (connect_rv == JVM_IO_ERR && errno == EINVAL) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
- "Invalid argument or cannot assign requested address");
- return;
- }
-#endif
- if (connect_rv == JVM_IO_INTR) {
- JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
- "operation interrupted");
-#if defined(EPROTO)
- } else if (errno == EPROTO) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ProtocolException",
- "Protocol error");
-#endif
- } else if (errno == ECONNREFUSED) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
- "Connection refused");
- } else if (errno == ETIMEDOUT) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
- "Connection timed out");
- } else if (errno == EHOSTUNREACH) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "NoRouteToHostException",
- "Host unreachable");
- } else if (errno == EADDRNOTAVAIL) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "NoRouteToHostException",
- "Address not available");
- } else if ((errno == EISCONN) || (errno == EBADF)) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
- "Socket closed");
- } else {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "connect failed");
- }
- return;
- }
-
- (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
-
- /* set the remote peer address and port */
- (*env)->SetObjectField(env, this, psi_addressID, iaObj);
- (*env)->SetIntField(env, this, psi_portID, port);
-
- /*
- * we need to initialize the local port field if bind was called
- * previously to the connect (by the client) then localport field
- * will already be initialized
- */
- if (localport == 0) {
- /* Now that we're a connected socket, let's extract the port number
- * that the system chose for us and store it in the Socket object.
- */
- len = SOCKADDR_LEN;
- if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
- "Error getting socket name");
- } else {
- localport = NET_GetPortFromSockaddr((struct sockaddr *)&him);
- (*env)->SetIntField(env, this, psi_localportID, localport);
- }
- }
-}
-
-/*
- * Class: java_net_PlainSocketImpl
- * Method: socketBind
- * Signature: (Ljava/net/InetAddress;I)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketBind(JNIEnv *env, jobject this,
- jobject iaObj, jint localport) {
-
- /* fdObj is the FileDescriptor field on this */
- jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
- /* fd is an int field on fdObj */
- int fd;
- int len;
- SOCKADDR him;
-
- if (IS_NULL(fdObj)) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
- "Socket closed");
- return;
- } else {
- fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
- }
- if (IS_NULL(iaObj)) {
- JNU_ThrowNullPointerException(env, "iaObj is null.");
- return;
- }
-
- /* bind */
- if (NET_InetAddressToSockaddr(env, iaObj, localport, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) {
- return;
- }
- setDefaultScopeID(env, (struct sockaddr *)&him);
-
- if (NET_Bind(fd, (struct sockaddr *)&him, len) < 0) {
- if (errno == EADDRINUSE || errno == EADDRNOTAVAIL ||
- errno == EPERM || errno == EACCES) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "BindException",
- "Bind failed");
- } else {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
- "Bind failed");
- }
- return;
- }
-
- /* set the address */
- (*env)->SetObjectField(env, this, psi_addressID, iaObj);
-
- /* initialize the local port */
- if (localport == 0) {
- /* Now that we're a connected socket, let's extract the port number
- * that the system chose for us and store it in the Socket object.
- */
- if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
- "Error getting socket name");
- return;
- }
- localport = NET_GetPortFromSockaddr((struct sockaddr *)&him);
- (*env)->SetIntField(env, this, psi_localportID, localport);
- } else {
- (*env)->SetIntField(env, this, psi_localportID, localport);
- }
-}
-
-/*
- * Class: java_net_PlainSocketImpl
- * Method: socketListen
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketListen (JNIEnv *env, jobject this,
- jint count)
-{
- /* this FileDescriptor fd field */
- jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
- /* fdObj's int fd field */
- int fd;
-
- if (IS_NULL(fdObj)) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
- "Socket closed");
- return;
- } else {
- fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
- }
-
- /*
- * Workaround for bugid 4101691 in Solaris 2.6. See 4106600.
- * If listen backlog is Integer.MAX_VALUE then subtract 1.
- */
- if (count == 0x7fffffff)
- count -= 1;
-
- if (JVM_Listen(fd, count) == JVM_IO_ERR) {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
- "Listen failed");
- }
-}
-
-/*
- * Class: java_net_PlainSocketImpl
- * Method: socketAccept
- * Signature: (Ljava/net/SocketImpl;)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketAccept(JNIEnv *env, jobject this,
- jobject socket)
-{
- /* fields on this */
- int port;
- jint timeout = (*env)->GetIntField(env, this, psi_timeoutID);
- jlong prevTime = 0;
- jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
-
- /* the FileDescriptor field on socket */
- jobject socketFdObj;
- /* the InetAddress field on socket */
- jobject socketAddressObj;
-
- /* the ServerSocket fd int field on fdObj */
- jint fd;
-
- /* accepted fd */
- jint newfd;
-
- SOCKADDR him;
- int len;
-
- len = SOCKADDR_LEN;
-
- if (IS_NULL(fdObj)) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
- "Socket closed");
- return;
- } else {
- fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
- }
- if (IS_NULL(socket)) {
- JNU_ThrowNullPointerException(env, "socket is null");
- return;
- }
-
- /*
- * accept connection but ignore ECONNABORTED indicating that
- * connection was eagerly accepted by the OS but was reset
- * before accept() was called.
- *
- * If accept timeout in place and timeout is adjusted with
- * each ECONNABORTED or EWOULDBLOCK to ensure that semantics
- * of timeout are preserved.
- */
- for (;;) {
- int ret;
-
- /* first usage pick up current time */
- if (prevTime == 0 && timeout > 0) {
- prevTime = JVM_CurrentTimeMillis(env, 0);
- }
-
- /* passing a timeout of 0 to poll will return immediately,
- but in the case of ServerSocket 0 means infinite. */
- if (timeout <= 0) {
- ret = NET_Timeout(fd, -1);
- } else {
- ret = NET_Timeout(fd, timeout);
- }
-
- if (ret == 0) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
- "Accept timed out");
- return;
- } else if (ret == JVM_IO_ERR) {
- if (errno == EBADF) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
- } else {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Accept failed");
- }
- return;
- } else if (ret == JVM_IO_INTR) {
- JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
- "operation interrupted");
- return;
- }
-
- newfd = NET_Accept(fd, (struct sockaddr *)&him, (jint*)&len);
-
- /* connection accepted */
- if (newfd >= 0) {
- SET_BLOCKING(newfd);
- break;
- }
-
- /* non (ECONNABORTED or EWOULDBLOCK) error */
- if (!(errno == ECONNABORTED || errno == EWOULDBLOCK)) {
- break;
- }
-
- /* ECONNABORTED or EWOULDBLOCK error so adjust timeout if there is one. */
- if (timeout) {
- jlong currTime = JVM_CurrentTimeMillis(env, 0);
- timeout -= (currTime - prevTime);
-
- if (timeout <= 0) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
- "Accept timed out");
- return;
- }
- prevTime = currTime;
- }
- }
-
- if (newfd < 0) {
- if (newfd == -2) {
- JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
- "operation interrupted");
- } else {
- if (errno == EINVAL) {
- errno = EBADF;
- }
- if (errno == EBADF) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
- } else {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Accept failed");
- }
- }
- return;
- }
-
- /*
- * fill up the remote peer port and address in the new socket structure.
- */
- socketAddressObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&him, &port);
- if (socketAddressObj == NULL) {
- /* should be pending exception */
- untagSocket(env, fd);
- close(newfd);
- return;
- }
-
- /*
- * Populate SocketImpl.fd.fd
- */
- socketFdObj = (*env)->GetObjectField(env, socket, psi_fdID);
- (*env)->SetIntField(env, socketFdObj, IO_fd_fdID, newfd);
-
- (*env)->SetObjectField(env, socket, psi_addressID, socketAddressObj);
- (*env)->SetIntField(env, socket, psi_portID, port);
- /* also fill up the local port information */
- port = (*env)->GetIntField(env, this, psi_localportID);
- (*env)->SetIntField(env, socket, psi_localportID, port);
-}
-
-
-/*
- * Class: java_net_PlainSocketImpl
- * Method: socketAvailable
- * Signature: ()I
- */
-JNIEXPORT jint JNICALL
-PlainSocketImpl_socketAvailable(JNIEnv *env, jobject this) {
-
- jint ret = -1;
- jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
- jint fd;
-
- if (IS_NULL(fdObj)) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
- "Socket closed");
- return -1;
- } else {
- fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
- }
- /* JVM_SocketAvailable returns 0 for failure, 1 for success */
- if (!JVM_SocketAvailable(fd, &ret)){
- if (errno == ECONNRESET) {
- JNU_ThrowByName(env, "sun/net/ConnectionResetException", "");
- } else {
- NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
- "ioctl FIONREAD failed");
- }
- }
- return ret;
-}
-
-/*
- * Class: java_net_PlainSocketImpl
- * Method: socketClose0
- * Signature: (Z)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketClose0(JNIEnv *env, jobject this,
- jboolean useDeferredClose) {
-
- jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
- jint fd;
-
- if (IS_NULL(fdObj)) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
- "socket already closed");
- return;
- } else {
- fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
- }
- if (fd != -1) {
- int marker_fd = -1;
- if (useDeferredClose) {
- marker_fd = getMarkerFD();
- }
- if (useDeferredClose && marker_fd >= 0) {
- NET_Dup2(marker_fd, fd);
- NET_SocketClose(marker_fd);
- } else {
- (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1);
- NET_SocketClose(fd);
- }
- }
-}
-
-/*
- * Class: java_net_PlainSocketImpl
- * Method: socketShutdown
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketShutdown(JNIEnv *env, jobject this,
- jint howto)
-{
-
- jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
- jint fd;
-
- /*
- * WARNING: THIS NEEDS LOCKING. ALSO: SHOULD WE CHECK for fd being
- * -1 already?
- */
- if (IS_NULL(fdObj)) {
- JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
- "socket already closed");
- return;
- } else {
- fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
- }
- JVM_SocketShutdown(fd, howto);
-}
-
-
-/*
- * Class: java_net_PlainSocketImpl
* Method: socketSetOption0
* Signature: (IZLjava/lang/Object;)V
*/
@@ -1067,56 +322,9 @@
}
-/*
- * Class: java_net_PlainSocketImpl
- * Method: socketSendUrgentData
- * Signature: (B)V
- */
-JNIEXPORT void JNICALL
-PlainSocketImpl_socketSendUrgentData(JNIEnv *env, jobject this,
- jint data) {
- /* The fd field */
- jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
- int n, fd;
- unsigned char d = data & 0xFF;
-
- if (IS_NULL(fdObj)) {
- JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
- return;
- } else {
- fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
- /* Bug 4086704 - If the Socket associated with this file descriptor
- * was closed (sysCloseFD), the the file descriptor is set to -1.
- */
- if (fd == -1) {
- JNU_ThrowByName(env, "java/net/SocketException", "Socket closed");
- return;
- }
-
- }
- n = JVM_Send(fd, (char *)&d, 1, MSG_OOB);
- if (n == JVM_IO_ERR) {
- NET_ThrowByNameWithLastError(env, "java/io/IOException", "Write failed");
- return;
- }
- if (n == JVM_IO_INTR) {
- JNU_ThrowByName(env, "java/io/InterruptedIOException", 0);
- return;
- }
-}
-
static JNINativeMethod gMethods[] = {
- NATIVE_METHOD(PlainSocketImpl, socketSendUrgentData, "(I)V"),
NATIVE_METHOD(PlainSocketImpl, socketGetOption, "(ILjava/lang/Object;)I"),
NATIVE_METHOD(PlainSocketImpl, socketSetOption0, "(IZLjava/lang/Object;)V"),
- NATIVE_METHOD(PlainSocketImpl, socketShutdown, "(I)V"),
- NATIVE_METHOD(PlainSocketImpl, socketClose0, "(Z)V"),
- NATIVE_METHOD(PlainSocketImpl, socketAccept, "(Ljava/net/SocketImpl;)V"),
- NATIVE_METHOD(PlainSocketImpl, socketAvailable, "()I"),
- NATIVE_METHOD(PlainSocketImpl, socketListen, "(I)V"),
- NATIVE_METHOD(PlainSocketImpl, socketBind, "(Ljava/net/InetAddress;I)V"),
- NATIVE_METHOD(PlainSocketImpl, socketConnect, "(Ljava/net/InetAddress;II)V"),
- NATIVE_METHOD(PlainSocketImpl, socketCreate, "(Z)V"),
};
void register_java_net_PlainSocketImpl(JNIEnv* env) {
diff --git a/ojluni/src/test/java/nio/file/attribute/AclFileAttributeViewTest.java b/ojluni/src/test/java/nio/file/attribute/AclFileAttributeViewTest.java
deleted file mode 100644
index 2d9e32c..0000000
--- a/ojluni/src/test/java/nio/file/attribute/AclFileAttributeViewTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (c) 2008, 2011, 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
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/* @test
- * @bug 4313887 6838333 6891404
- * @summary Unit test for java.nio.file.attribute.AclFileAttribueView
- * @library ../..
- */
-// Android-changed: Adapted from
-// jdk/test/java/nio/file/attribute/AclFileAttributeView/Basic.java
-// Android-changed: Added package & Test import
-package test.java.nio.file.attribute;
-import org.testng.annotations.Test;
-import test.java.nio.file.TestUtil;
-
-import java.nio.file.*;
-import java.nio.file.attribute.*;
-import java.io.IOException;
-import java.util.*;
-
-import static java.nio.file.attribute.AclEntryType.*;
-import static java.nio.file.attribute.AclEntryPermission.*;
-import static java.nio.file.attribute.AclEntryFlag.*;
-
-// Android-changed: Renamed from "Basic"
-public class AclFileAttributeViewTest {
-
- static void printAcl(List<AclEntry> acl) {
- for (AclEntry entry: acl) {
- System.out.format(" %s%n", entry);
- }
- }
-
- // sanity check read and writing ACL
- static void testReadWrite(Path dir) throws IOException {
- Path file = dir.resolve("foo");
- if (Files.notExists(file))
- Files.createFile(file);
-
- AclFileAttributeView view =
- Files.getFileAttributeView(file, AclFileAttributeView.class);
-
- // print existing ACL
- List<AclEntry> acl = view.getAcl();
- System.out.println(" -- current ACL --");
- printAcl(acl);
-
- // insert entry to grant owner read access
- UserPrincipal owner = view.getOwner();
- AclEntry entry = AclEntry.newBuilder()
- .setType(ALLOW)
- .setPrincipal(owner)
- .setPermissions(READ_DATA, READ_ATTRIBUTES)
- .build();
- System.out.println(" -- insert (entry 0) --");
- System.out.format(" %s%n", entry);
- acl.add(0, entry);
- view.setAcl(acl);
-
- // re-ACL and check entry
- List<AclEntry> newacl = view.getAcl();
- System.out.println(" -- current ACL --");
- printAcl(acl);
- if (!newacl.get(0).equals(entry)) {
- throw new RuntimeException("Entry 0 is not expected");
- }
-
- // if PosixFileAttributeView then repeat test with OWNER@
- if (Files.getFileStore(file).supportsFileAttributeView("posix")) {
- owner = file.getFileSystem().getUserPrincipalLookupService()
- .lookupPrincipalByName("OWNER@");
- entry = AclEntry.newBuilder(entry).setPrincipal(owner).build();
-
- System.out.println(" -- replace (entry 0) --");
- System.out.format(" %s%n", entry);
-
- acl.set(0, entry);
- view.setAcl(acl);
- newacl = view.getAcl();
- System.out.println(" -- current ACL --");
- printAcl(acl);
- if (!newacl.get(0).equals(entry)) {
- throw new RuntimeException("Entry 0 is not expected");
- }
- }
- }
-
- static FileAttribute<List<AclEntry>> asAclAttribute(final List<AclEntry> acl) {
- return new FileAttribute<List<AclEntry>>() {
- public String name() { return "acl:acl"; }
- public List<AclEntry> value() { return acl; }
- };
- }
-
- static void assertEquals(List<AclEntry> actual, List<AclEntry> expected) {
- if (!actual.equals(expected)) {
- System.err.format("Actual: %s\n", actual);
- System.err.format("Expected: %s\n", expected);
- throw new RuntimeException("ACL not expected");
- }
- }
-
- // sanity check create a file or directory with initial ACL
- static void testCreateFile(Path dir) throws IOException {
- UserPrincipal user = Files.getOwner(dir);
- AclFileAttributeView view;
-
- // create file with initial ACL
- System.out.println("-- create file with initial ACL --");
- Path file = dir.resolve("gus");
- List<AclEntry> fileAcl = Arrays.asList(
- AclEntry.newBuilder()
- .setType(AclEntryType.ALLOW)
- .setPrincipal(user)
- .setPermissions(SYNCHRONIZE, READ_DATA, WRITE_DATA,
- READ_ATTRIBUTES, READ_ACL, WRITE_ATTRIBUTES, DELETE)
- .build());
- Files.createFile(file, asAclAttribute(fileAcl));
- view = Files.getFileAttributeView(file, AclFileAttributeView.class);
- assertEquals(view.getAcl(), fileAcl);
-
- // create directory with initial ACL
- System.out.println("-- create directory with initial ACL --");
- Path subdir = dir.resolve("stuff");
- List<AclEntry> dirAcl = Arrays.asList(
- AclEntry.newBuilder()
- .setType(AclEntryType.ALLOW)
- .setPrincipal(user)
- .setPermissions(SYNCHRONIZE, ADD_FILE, DELETE)
- .build(),
- AclEntry.newBuilder(fileAcl.get(0))
- .setFlags(FILE_INHERIT)
- .build());
- Files.createDirectory(subdir, asAclAttribute(dirAcl));
- view = Files.getFileAttributeView(subdir, AclFileAttributeView.class);
- assertEquals(view.getAcl(), dirAcl);
- }
-
- // Android-changed: Removed args & added @Test
- @Test
- public static void main() throws IOException {
- // use work directory rather than system temporary directory to
- // improve chances that ACLs are supported
- // Android-changed: Switched to temp dir due to permissions
- // Path dir = Paths.get("./work" + new Random().nextInt());
- // Files.createTempDirectory(dir);
- Path dir = Files.createTempDirectory("acl");
- try {
- if (!Files.getFileStore(dir).supportsFileAttributeView("acl")) {
- System.out.println("ACLs not supported - test skipped!");
- return;
- }
- testReadWrite(dir);
-
- // only currently feasible on Windows
- if (System.getProperty("os.name").startsWith("Windows"))
- testCreateFile(dir);
-
- } finally {
- TestUtil.removeAll(dir);
- }
- }
-}
diff --git a/ojluni/src/test/java/nio/file/attribute/BasicFileAttributeViewCreationTimeTest.java b/ojluni/src/test/java/nio/file/attribute/BasicFileAttributeViewCreationTimeTest.java
index b393981..c2a30bb 100644
--- a/ojluni/src/test/java/nio/file/attribute/BasicFileAttributeViewCreationTimeTest.java
+++ b/ojluni/src/test/java/nio/file/attribute/BasicFileAttributeViewCreationTimeTest.java
@@ -80,15 +80,17 @@
boolean supportsCreationTimeRead = false;
boolean supportsCreationTimeWrite = false;
String os = System.getProperty("os.name");
- if (os.contains("OS X") && Files.getFileStore(file).type().equals("hfs")) {
- supportsCreationTimeRead = true;
- } else if (os.startsWith("Windows")) {
- String type = Files.getFileStore(file).type();
- if (type.equals("NTFS") || type.equals("FAT")) {
- supportsCreationTimeRead = true;
- supportsCreationTimeWrite = true;
- }
- }
+ // Android-changed: This test is never run on Mac OS or windows hosts.
+ //
+ // if (os.contains("OS X") && Files.getFileStore(file).type().equals("hfs")) {
+ // supportsCreationTimeRead = true;
+ // } else if (os.startsWith("Windows")) {
+ // String type = Files.getFileStore(file).type();
+ // if (type.equals("NTFS") || type.equals("FAT")) {
+ // supportsCreationTimeRead = true;
+ // supportsCreationTimeWrite = true;
+ // }
+ // }
/**
* If the creation-time attribute is supported then change the file's
diff --git a/ojluni/src/test/java/nio/file/attribute/DosFileAttributeViewTest.java b/ojluni/src/test/java/nio/file/attribute/DosFileAttributeViewTest.java
deleted file mode 100644
index 8386a31..0000000
--- a/ojluni/src/test/java/nio/file/attribute/DosFileAttributeViewTest.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (c) 2008, 2011, 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
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/* @test
- * @bug 4313887 6838333
- * @summary Unit test for java.nio.file.attribute.DosFileAttributeView
- * @library ../..
- */
-// Android-changed: Adapted from
-// jdk/test/java/nio/file/attribute/DosFileAttributeView/Basic.java
-// Android-changed: Added package & Test import
-package test.java.nio.file.attribute;
-import org.testng.annotations.Test;
-import test.java.nio.file.TestUtil;
-
-import java.nio.file.*;
-import static java.nio.file.LinkOption.*;
-import java.nio.file.attribute.*;
-import java.util.*;
-import java.io.IOException;
-
-// Android-changed: Renamed from "Basic"
-public class DosFileAttributeViewTest {
-
- static void check(boolean okay) {
- if (!okay)
- throw new RuntimeException("Test failed");
- }
-
- // exercise each setter/getter method, leaving all attributes unset
- static void testAttributes(DosFileAttributeView view) throws IOException {
- view.setReadOnly(true);
- check(view.readAttributes().isReadOnly());
- view.setReadOnly(false);
- check(!view.readAttributes().isReadOnly());
- view.setHidden(true);
- check(view.readAttributes().isHidden());
- view.setHidden(false);
- check(!view.readAttributes().isHidden());
- view.setArchive(true);
- check(view.readAttributes().isArchive());
- view.setArchive(false);
- check(!view.readAttributes().isArchive());
- view.setSystem(true);
- check(view.readAttributes().isSystem());
- view.setSystem(false);
- check(!view.readAttributes().isSystem());
- }
-
- // set the value of all attributes
- static void setAll(DosFileAttributeView view, boolean value)
- throws IOException
- {
- view.setReadOnly(value);
- view.setHidden(value);
- view.setArchive(value);
- view.setSystem(value);
- }
-
- // read and write FAT attributes
- static void readWriteTests(Path dir) throws IOException {
-
- // create "foo" and test that we can read/write each FAT attribute
- Path file = Files.createFile(dir.resolve("foo"));
- try {
- testAttributes(Files.getFileAttributeView(file, DosFileAttributeView.class));
-
- // Following tests use a symbolic link so skip if not supported
- if (!TestUtil.supportsLinks(dir))
- return;
-
- Path link = dir.resolve("link");
- Files.createSymbolicLink(link, file);
-
- // test following links
- testAttributes(Files.getFileAttributeView(link, DosFileAttributeView.class));
-
- // test not following links
- try {
- try {
- testAttributes(Files
- .getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS));
- } catch (IOException x) {
- // access to link attributes not supported
- return;
- }
-
- // set all attributes on link
- // run test on target of link (which leaves them all un-set)
- // check that attributes of link remain all set
- setAll(Files
- .getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS), true);
- testAttributes(Files
- .getFileAttributeView(link, DosFileAttributeView.class));
- DosFileAttributes attrs =
- Files.getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS)
- .readAttributes();
- check(attrs.isReadOnly());
- check(attrs.isHidden());
- check(attrs.isArchive());
- check(attrs.isSystem());
- setAll(Files
- .getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS), false);
-
- // set all attributes on target
- // run test on link (which leaves them all un-set)
- // check that attributes of target remain all set
- setAll(Files.getFileAttributeView(link, DosFileAttributeView.class), true);
- testAttributes(Files
- .getFileAttributeView(link, DosFileAttributeView.class, NOFOLLOW_LINKS));
- attrs = Files.getFileAttributeView(link, DosFileAttributeView.class).readAttributes();
- check(attrs.isReadOnly());
- check(attrs.isHidden());
- check(attrs.isArchive());
- check(attrs.isSystem());
- setAll(Files.getFileAttributeView(link, DosFileAttributeView.class), false);
- } finally {
- TestUtil.deleteUnchecked(link);
- }
- } finally {
- TestUtil.deleteUnchecked(file);
- }
- }
-
- // Android-changed: Removed args & added @Test
- @Test
- public static void main() throws IOException {
- // create temporary directory to run tests
- Path dir = TestUtil.createTemporaryDirectory();
-
- try {
- // skip test if DOS file attributes not supported
- if (!Files.getFileStore(dir).supportsFileAttributeView("dos")) {
- System.out.println("DOS file attribute not supported.");
- return;
- }
- readWriteTests(dir);
- } finally {
- TestUtil.removeAll(dir);
- }
- }
-}
diff --git a/ojluni/src/test/java/nio/file/attribute/PosixFileAttributeViewTest.java b/ojluni/src/test/java/nio/file/attribute/PosixFileAttributeViewTest.java
index 7825837..4355b31 100644
--- a/ojluni/src/test/java/nio/file/attribute/PosixFileAttributeViewTest.java
+++ b/ojluni/src/test/java/nio/file/attribute/PosixFileAttributeViewTest.java
@@ -388,10 +388,13 @@
public static void main() throws IOException {
Path dir = TestUtil.createTemporaryDirectory();
try {
- if (!Files.getFileStore(dir).supportsFileAttributeView("posix")) {
- System.out.println("PosixFileAttributeView not supported");
- return;
- }
+ // Android-changed: PosixFileAttributeViews are unconditionally supported in
+ // all writeable partitions.
+ //
+ // if (!Files.getFileStore(dir).supportsFileAttributeView("posix")) {
+ // System.out.println("PosixFileAttributeView not supported");
+ // return;
+ // }
permissionTests(dir);
createTests(dir);
diff --git a/ojluni/src/test/java/nio/file/attribute/UserDefinedFileAttributeViewTest.java b/ojluni/src/test/java/nio/file/attribute/UserDefinedFileAttributeViewTest.java
deleted file mode 100644
index 6fb3c78..0000000
--- a/ojluni/src/test/java/nio/file/attribute/UserDefinedFileAttributeViewTest.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (c) 2008, 2011, 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
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/* @test
- * @bug 4313887 6838333
- * @summary Unit test for java.nio.file.attribute.UserDefinedFileAttributeView
- * @library ../..
- */
-// Android-changed: Adapted from
-// jdk/test/java/nio/file/attribute/UserDefinedFileAttributeView/Basic.java
-// Android-changed: Added package & Test import
-package test.java.nio.file.attribute;
-import org.testng.annotations.Test;
-import test.java.nio.file.TestUtil;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.file.*;
-import static java.nio.file.LinkOption.*;
-import java.nio.file.attribute.*;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Random;
-import java.io.IOException;
-
-// Android-changed: Renamed from "Basic"
-public class UserDefinedFileAttributeViewTest {
-
- private static Random rand = new Random();
-
- private static final String ATTR_NAME = "mime_type";
- private static final String ATTR_VALUE = "text/plain";
- private static final String ATTR_VALUE2 = "text/html";
-
- static interface Task {
- void run() throws Exception;
- }
-
- static void tryCatch(Class<? extends Throwable> ex, Task task) {
- boolean caught = false;
- try {
- task.run();
- } catch (Throwable x) {
- if (ex.isAssignableFrom(x.getClass())) {
- caught = true;
- } else {
- throw new RuntimeException(x);
- }
- }
- if (!caught)
- throw new RuntimeException(ex.getName() + " expected");
- }
-
- static void expectNullPointerException(Task task) {
- tryCatch(NullPointerException.class, task);
- }
-
- static boolean hasAttribute(UserDefinedFileAttributeView view, String attr)
- throws IOException
- {
- for (String name: view.list()) {
- if (name.equals(ATTR_NAME))
- return true;
- }
- return false;
- }
-
- static void test(Path file, LinkOption... options) throws IOException {
- final UserDefinedFileAttributeView view =
- Files.getFileAttributeView(file, UserDefinedFileAttributeView.class, options);
- ByteBuffer buf = rand.nextBoolean() ?
- ByteBuffer.allocate(100) : ByteBuffer.allocateDirect(100);
-
- // Test: write
- buf.put(ATTR_VALUE.getBytes()).flip();
- int size = buf.remaining();
- int nwrote = view.write(ATTR_NAME, buf);
- if (nwrote != size)
- throw new RuntimeException("Unexpected number of bytes written");
-
- // Test: size
- if (view.size(ATTR_NAME) != size)
- throw new RuntimeException("Unexpected size");
-
- // Test: read
- buf.clear();
- int nread = view.read(ATTR_NAME, buf);
- if (nread != size)
- throw new RuntimeException("Unexpected number of bytes read");
- buf.flip();
- String value = Charset.defaultCharset().decode(buf).toString();
- if (!value.equals(ATTR_VALUE))
- throw new RuntimeException("Unexpected attribute value");
-
- // Test: read with insufficient space
- tryCatch(IOException.class, new Task() {
- public void run() throws IOException {
- view.read(ATTR_NAME, ByteBuffer.allocateDirect(1));
- }});
-
- // Test: replace value
- buf.clear();
- buf.put(ATTR_VALUE2.getBytes()).flip();
- size = buf.remaining();
- view.write(ATTR_NAME, buf);
- if (view.size(ATTR_NAME) != size)
- throw new RuntimeException("Unexpected size");
-
- // Test: list
- if (!hasAttribute(view, ATTR_NAME))
- throw new RuntimeException("Attribute name not in list");
-
- // Test: delete
- view.delete(ATTR_NAME);
- if (hasAttribute(view, ATTR_NAME))
- throw new RuntimeException("Attribute name in list");
-
- // Test: dynamic access
- String name = "user:" + ATTR_NAME;
- byte[] valueAsBytes = ATTR_VALUE.getBytes();
- Files.setAttribute(file, name, valueAsBytes);
- byte[] actualAsBytes = (byte[])Files.getAttribute(file, name);
- if (!Arrays.equals(valueAsBytes, actualAsBytes))
- throw new RuntimeException("Unexpected attribute value");
- Map<String,?> map = Files.readAttributes(file, name);
- if (!Arrays.equals(valueAsBytes, (byte[])map.get(ATTR_NAME)))
- throw new RuntimeException("Unexpected attribute value");
- map = Files.readAttributes(file, "user:*");
- if (!Arrays.equals(valueAsBytes, (byte[])map.get(ATTR_NAME)))
- throw new RuntimeException("Unexpected attribute value");
- }
-
- static void miscTests(final Path file) throws IOException {
- final UserDefinedFileAttributeView view =
- Files.getFileAttributeView(file, UserDefinedFileAttributeView.class);
- view.write(ATTR_NAME, ByteBuffer.wrap(ATTR_VALUE.getBytes()));
-
- // NullPointerException
- final ByteBuffer buf = ByteBuffer.allocate(100);
-
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- view.read(null, buf);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- view.read(ATTR_NAME, null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- view.write(null, buf);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- view.write(ATTR_NAME, null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- view.size(null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- view.delete(null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- Files.getAttribute(file, null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- Files.getAttribute(file, "user:" + ATTR_NAME, (LinkOption[])null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- Files.setAttribute(file, "user:" + ATTR_NAME, null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- Files.setAttribute(file, null, new byte[0]);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- Files.setAttribute(file, "user: " + ATTR_NAME, new byte[0], (LinkOption[])null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- Files.readAttributes(file, (String)null);
- }});
- expectNullPointerException(new Task() {
- public void run() throws IOException {
- Files.readAttributes(file, "*", (LinkOption[])null);
- }});
-
- // Read-only buffer
- tryCatch(IllegalArgumentException.class, new Task() {
- public void run() throws IOException {
- ByteBuffer buf = ByteBuffer.wrap(ATTR_VALUE.getBytes()).asReadOnlyBuffer();
- view.write(ATTR_NAME, buf);
- buf.flip();
- view.read(ATTR_NAME, buf);
- }});
-
- // Zero bytes remaining
- tryCatch(IOException.class, new Task() {
- public void run() throws IOException {
- ByteBuffer buf = buf = ByteBuffer.allocateDirect(100);
- buf.position(buf.capacity());
- view.read(ATTR_NAME, buf);
- }});
- }
-
- // Android-changed: Removed args & added @Test
- @Test
- public static void main() throws IOException {
- // create temporary directory to run tests
- Path dir = TestUtil.createTemporaryDirectory();
- try {
- if (!Files.getFileStore(dir).supportsFileAttributeView("user")) {
- System.out.println("UserDefinedFileAttributeView not supported - skip test");
- return;
- }
-
- // test access to user defined attributes of regular file
- Path file = dir.resolve("foo.html");
- Files.createFile(file);
- try {
- test(file);
- } finally {
- Files.delete(file);
- }
-
- // test access to user defined attributes of directory
- Path subdir = dir.resolve("foo");
- Files.createDirectory(subdir);
- try {
- test(subdir);
- } finally {
- Files.delete(subdir);
- }
-
- // test access to user defined attributes of sym link
- if (TestUtil.supportsLinks(dir)) {
- Path target = dir.resolve("doesnotexist");
- Path link = dir.resolve("link");
- Files.createSymbolicLink(link, target);
- try {
- test(link, NOFOLLOW_LINKS);
- } catch (IOException x) {
- // access to attributes of sym link may not be supported
- } finally {
- Files.delete(link);
- }
- }
-
- // misc. tests
- try {
- file = dir.resolve("foo.txt");
- Files.createFile(file);
- miscTests(dir);
- } finally {
- Files.delete(file);
- }
-
- } finally {
- TestUtil.removeAll(dir);
- }
- }
- }
diff --git a/openjdk_java_files.mk b/openjdk_java_files.mk
index 4285911..3b38617 100644
--- a/openjdk_java_files.mk
+++ b/openjdk_java_files.mk
@@ -1610,6 +1610,8 @@
ojluni/src/main/java/sun/security/provider/certpath/X509CertificatePair.java \
ojluni/src/main/java/sun/security/provider/X509Factory.java \
ojluni/src/main/java/sun/security/timestamp/TimestampToken.java \
+ ojluni/src/main/java/sun/security/util/AnchorCertificates.java \
+ ojluni/src/main/java/sun/security/util/CertConstraintParameters.java \
ojluni/src/main/java/sun/security/util/AbstractAlgorithmConstraints.java \
ojluni/src/main/java/sun/security/util/AlgorithmDecomposer.java \
ojluni/src/main/java/sun/security/util/BitArray.java \
diff --git a/tools/upstream/oj_upstream_comparison.py b/tools/upstream/oj_upstream_comparison.py
index 312483c..deab5fb 100755
--- a/tools/upstream/oj_upstream_comparison.py
+++ b/tools/upstream/oj_upstream_comparison.py
@@ -46,10 +46,12 @@
"""
import argparse
+import csv
import filecmp
import os
import re
import shutil
+import sys
def rel_paths_from_makefile(build_top):
"""Returns the list of relative paths to .java files parsed from openjdk_java_files.mk"""
@@ -83,28 +85,75 @@
return result
return None
-def compare_to_upstreams(build_top, upstream_root, upstreams, rel_paths):
+
+# For files with N and M lines, respectively, this runs in time
+# O(N+M) if the files are identical or O(N*M) if not. This could
+# be improved to O(D*(N+M)) for files with at most D lines
+# difference by only considering array elements within D cells
+# from the diagonal.
+def edit_distance_lines(file_a, file_b):
"""
- Returns a dict from rel_path to lists of length len(upstreams)
- Each list entry specifies whether the file at a particular
- rel_path is missing from, identical to, or different from
- a particular upstream.
+ Computes the line-based edit distance between two text files, i.e.
+ the smallest number of line deletions, additions or replacements
+ that would transform the content of one file into that of the other.
"""
- result = {}
+ if filecmp.cmp(file_a, file_b, shallow=False):
+ return 0 # files identical
+ with open(file_a) as f:
+ lines_a = f.readlines()
+ with open(file_b) as f:
+ lines_b = f.readlines()
+ prev_cost = range(0, len(lines_b) + 1)
+ for end_a in range(1, len(lines_a) + 1):
+ # For each valid index i, prev_cost[i] is the edit distance between
+ # lines_a[:end_a-1] and lines_b[:i].
+ # We now calculate cur_cost[end_b] as the edit distance between
+ # line_a[:end_a] and lines_b[:end_b]
+ cur_cost = [end_a]
+ for end_b in range(1, len(lines_b) + 1):
+ c = min(
+ cur_cost[-1] + 1, # append line from b
+ prev_cost[end_b] + 1, # append line from a
+ # match or replace line
+ prev_cost[end_b - 1] + (0 if lines_a[end_a - 1] == lines_b[end_b - 1] else 1)
+ )
+ cur_cost.append(c)
+ prev_cost = cur_cost
+ return prev_cost[-1]
+
+def compare_to_upstreams_and_save(out_file, build_top, upstream_root, upstreams, rel_paths, best_only=False):
+ """
+ Prints tab-separated values comparing ojluni files vs. each
+ upstream, for each of the rel_paths, suitable for human
+ analysis in a spreadsheet.
+ This includes whether the corresponding upstream file is
+ missing, identical, or by how many lines it differs, and
+ a guess as to the correct upstream based on minimal line
+ difference (ties broken in favor of upstreams that occur
+ earlier in the list).
+ """
+ writer = csv.writer(out_file, delimiter='\t')
+ writer.writerow(["rel_path", "guessed_upstream"] + upstreams)
for rel_path in rel_paths:
ojluni_file = ojluni_path(build_top, rel_path)
- status = []
+ upstream_comparisons = []
+ best_distance = sys.maxint
+ guessed_upstream = ""
for upstream in upstreams:
upstream_file = upstream_path(upstream_root, upstream, rel_path)
if upstream_file is None:
- upstream_status = "missing"
- elif filecmp.cmp(upstream_file, ojluni_file, shallow=False):
- upstream_status = "identical"
+ upstream_comparison = "missing"
else:
- upstream_status = "different"
- status.append(upstream_status)
- result[rel_path] = status
- return result
+ edit_distance = edit_distance_lines(upstream_file, ojluni_file)
+ if edit_distance == 0:
+ upstream_comparison = "identical"
+ else:
+ upstream_comparison = "different (%d lines)" % (edit_distance)
+ if edit_distance < best_distance:
+ best_distance = edit_distance
+ guessed_upstream = upstream
+ upstream_comparisons.append(upstream_comparison)
+ writer.writerow([rel_path, guessed_upstream ] + upstream_comparisons)
def copy_files(rel_paths, upstream_root, upstream, output_dir):
"""Copies files at the given rel_paths from upstream to output_dir"""
@@ -148,13 +197,12 @@
raise Exception("Upstream not found: " + upstream_path)
rel_paths = rel_paths_from_makefile(args.build_top)
- upstream_infos = compare_to_upstreams(args.build_top, args.upstream_root, upstreams, rel_paths)
+
+ compare_to_upstreams_and_save(
+ sys.stdout, args.build_top, args.upstream_root, upstreams, rel_paths)
if args.output_dir is not None:
copy_files(rel_paths, args.upstream_root, default_upstream, args.output_dir)
- for rel_path in rel_paths:
- print(rel_path + "\t" + "\t".join(upstream_infos[rel_path]))
-
if __name__ == '__main__':
main()
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
index 3902d36..80f37bc 100644
--- a/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/DistroVersion.java
@@ -152,6 +152,15 @@
}
@Override
+ public int hashCode() {
+ int result = formatMajorVersion;
+ result = 31 * result + formatMinorVersion;
+ result = 31 * result + rulesVersion.hashCode();
+ result = 31 * result + revision;
+ return result;
+ }
+
+ @Override
public String toString() {
return "DistroVersion{" +
"formatMajorVersion=" + formatMajorVersion +
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
index a170a9e..39626ac 100644
--- a/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/FileUtils.java
@@ -17,6 +17,8 @@
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
@@ -173,4 +175,14 @@
return toReturn;
}
}
+
+ /**
+ * Creates an empty file.
+ *
+ * @param file the file to create
+ * @throws IOException if the file cannot be created
+ */
+ public static void createEmptyFile(File file) throws IOException {
+ new FileOutputStream(file, false /* append */).close();
+ }
}
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java
new file mode 100644
index 0000000..0492e12
--- /dev/null
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/StagedDistroOperation.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.tzdata.shared2;
+
+/**
+ * Information about a staged time zone distro operation.
+ */
+public class StagedDistroOperation {
+
+ private static final StagedDistroOperation UNINSTALL_STAGED =
+ new StagedDistroOperation(true /* isUninstall */, null /* stagedVersion */);
+
+ public final boolean isUninstall;
+ public final DistroVersion distroVersion;
+
+ private StagedDistroOperation(boolean isUninstall, DistroVersion distroVersion) {
+ this.isUninstall = isUninstall;
+ this.distroVersion = distroVersion;
+ }
+
+ public static StagedDistroOperation install(DistroVersion distroVersion) {
+ if (distroVersion == null) {
+ throw new NullPointerException("distroVersion==null");
+ }
+ return new StagedDistroOperation(false /* isUninstall */, distroVersion);
+ }
+
+ public static StagedDistroOperation uninstall() {
+ return UNINSTALL_STAGED;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ StagedDistroOperation that = (StagedDistroOperation) o;
+
+ if (isUninstall != that.isUninstall) {
+ return false;
+ }
+ return distroVersion != null ? distroVersion.equals(that.distroVersion)
+ : that.distroVersion == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (isUninstall ? 1 : 0);
+ result = 31 * result + (distroVersion != null ? distroVersion.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "StagedDistroOperation{" +
+ "isUninstall=" + isUninstall +
+ ", distroVersion=" + distroVersion +
+ '}';
+ }
+}
diff --git a/tzdata/shared2/src/main/libcore/tzdata/shared2/TimeZoneDistro.java b/tzdata/shared2/src/main/libcore/tzdata/shared2/TimeZoneDistro.java
index dd01fb0..9358c70 100644
--- a/tzdata/shared2/src/main/libcore/tzdata/shared2/TimeZoneDistro.java
+++ b/tzdata/shared2/src/main/libcore/tzdata/shared2/TimeZoneDistro.java
@@ -37,6 +37,9 @@
/** The name of the file inside the distro containing ICU TZ data. */
public static final String ICU_DATA_FILE_NAME = "icu/icu_tzdata.dat";
+ /** The name of the file inside the distro containing time zone lookup data. */
+ public static final String TZLOOKUP_FILE_NAME = "tzlookup.xml";
+
/**
* The name of the file inside the distro containing the distro version information.
* The content is ASCII bytes representing a set of version numbers. See {@link DistroVersion}.
diff --git a/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java b/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
index 3b9ec5b..87e6e6e 100644
--- a/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
+++ b/tzdata/shared2/src/test/libcore/tzdata/shared2/FileUtilsTest.java
@@ -253,6 +253,43 @@
assertTrue(Arrays.equals(contents, exhaustedFileRead));
}
+ public void testCreateEmptyFile() throws Exception {
+ File dir = createTempDir();
+ File file = new File(dir, "one");
+ assertFalse(file.exists());
+ FileUtils.createEmptyFile(file);
+ assertTrue(file.exists());
+ assertEquals(0, file.length());
+ }
+
+ public void testCreateEmptyFile_isDir() throws Exception {
+ File dir = createTempDir();
+ assertTrue(dir.exists());
+ assertTrue(dir.isDirectory());
+
+ try {
+ FileUtils.createEmptyFile(dir);
+ } catch (FileNotFoundException expected) {
+ }
+ assertTrue(dir.exists());
+ assertTrue(dir.isDirectory());
+ }
+
+ public void testCreateEmptyFile_truncatesExisting() throws Exception {
+ File dir = createTempDir();
+ File file = new File(dir, "one");
+
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(new byte[1000]);
+ }
+ assertTrue(file.exists());
+ assertEquals(1000, file.length());
+
+ FileUtils.createEmptyFile(file);
+ assertTrue(file.exists());
+ assertEquals(0, file.length());
+ }
+
private File createFile(byte[] contents) throws IOException {
File file = File.createTempFile(getClass().getSimpleName(), ".txt");
try (FileOutputStream fos = new FileOutputStream(file)) {
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneDistro.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneDistro.java
index 4b70152..7fabf7a 100644
--- a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneDistro.java
+++ b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneDistro.java
@@ -30,7 +30,7 @@
* A command-line tool for creating a timezone update distro.
*
* Args:
- * tzdata.properties file - the file describing the distro (see template file in tzdata/tools)
+ * tzdata.properties file - the file describing the distro (see template file in tzdata/tools2)
* output file - the name of the file to be generated
*/
public class CreateTimeZoneDistro {
@@ -56,8 +56,9 @@
Integer.parseInt(getMandatoryProperty(p, "revision")));
TimeZoneDistroBuilder builder = new TimeZoneDistroBuilder()
.setDistroVersion(distroVersion)
- .setTzData(getMandatoryPropertyFile(p, "bionic.file"))
- .setIcuData(getMandatoryPropertyFile(p, "icu.file"));
+ .setTzDataFile(getMandatoryPropertyFile(p, "bionic.file"))
+ .setIcuDataFile(getMandatoryPropertyFile(p, "icu.file"))
+ .setTzLookupFile(getMandatoryPropertyFile(p, "tzlookup.file"));
TimeZoneDistro distro = builder.build();
File outputFile = new File(args[1]);
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneDistroBuilder.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneDistroBuilder.java
index 4c12e96..9860489 100644
--- a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneDistroBuilder.java
+++ b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneDistroBuilder.java
@@ -19,6 +19,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import libcore.tzdata.shared2.DistroException;
@@ -34,6 +35,7 @@
private DistroVersion distroVersion;
private byte[] tzData;
private byte[] icuData;
+ private String tzLookupXml;
public TimeZoneDistroBuilder setDistroVersion(DistroVersion distroVersion) {
this.distroVersion = distroVersion;
@@ -56,11 +58,11 @@
return this;
}
- public TimeZoneDistroBuilder setTzData(File tzDataFile) throws IOException {
- return setTzData(readFileAsByteArray(tzDataFile));
+ public TimeZoneDistroBuilder setTzDataFile(File tzDataFile) throws IOException {
+ return setTzDataFile(readFileAsByteArray(tzDataFile));
}
- public TimeZoneDistroBuilder setTzData(byte[] tzData) {
+ public TimeZoneDistroBuilder setTzDataFile(byte[] tzData) {
this.tzData = tzData;
return this;
}
@@ -71,15 +73,24 @@
return this;
}
- public TimeZoneDistroBuilder setIcuData(File icuDataFile) throws IOException {
- return setIcuData(readFileAsByteArray(icuDataFile));
+ public TimeZoneDistroBuilder setIcuDataFile(File icuDataFile) throws IOException {
+ return setIcuDataFile(readFileAsByteArray(icuDataFile));
}
- public TimeZoneDistroBuilder setIcuData(byte[] icuData) {
+ public TimeZoneDistroBuilder setIcuDataFile(byte[] icuData) {
this.icuData = icuData;
return this;
}
+ public TimeZoneDistroBuilder setTzLookupFile(File tzLookupFile) throws IOException {
+ return setTzLookupXml(readFileAsUtf8(tzLookupFile));
+ }
+
+ public TimeZoneDistroBuilder setTzLookupXml(String tzlookupXml) {
+ this.tzLookupXml = tzlookupXml;
+ return this;
+ }
+
// For use in tests.
public TimeZoneDistroBuilder clearIcuDataForTests() {
this.icuData = null;
@@ -102,6 +113,10 @@
if (icuData != null) {
addZipEntry(zos, TimeZoneDistro.ICU_DATA_FILE_NAME, icuData);
}
+ if (tzLookupXml != null) {
+ addZipEntry(zos, TimeZoneDistro.TZLOOKUP_FILE_NAME,
+ tzLookupXml.getBytes(StandardCharsets.UTF_8));
+ }
} catch (IOException e) {
throw new DistroException("Unable to create zip file", e);
}
@@ -140,7 +155,7 @@
/**
* Returns the contents of 'path' as a byte array.
*/
- public static byte[] readFileAsByteArray(File file) throws IOException {
+ private static byte[] readFileAsByteArray(File file) throws IOException {
byte[] buffer = new byte[8192];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (FileInputStream fis = new FileInputStream(file)) {
@@ -151,5 +166,12 @@
}
return baos.toByteArray();
}
+
+ /**
+ * Returns the contents of 'path' as a String, having interpreted the file as UTF-8.
+ */
+ private String readFileAsUtf8(File file) throws IOException {
+ return new String(readFileAsByteArray(file), StandardCharsets.UTF_8);
+ }
}
diff --git a/tzdata/tools2/testing/prepareTzDataUpdates.sh b/tzdata/tools2/testing/prepareTzDataUpdates.sh
index 5b9b2ab..9a41942 100755
--- a/tzdata/tools2/testing/prepareTzDataUpdates.sh
+++ b/tzdata/tools2/testing/prepareTzDataUpdates.sh
@@ -26,6 +26,7 @@
# Get the current tzdata version and find both the previous and new versions.
TZDATA=libc/zoneinfo/tzdata
+TZLOOKUP=libc/zoneinfo/tzlookup.xml
TZHEADER=$(head -n1 bionic/$TZDATA | cut -c1-11)
@@ -88,6 +89,7 @@
rules.version=${TZ_PREVIOUS}
bionic.file=${TMP_PREVIOUS}/tzdata
icu.file=${TMP_PREVIOUS}/icu_tzdata.dat
+tzlookup.file=${ANDROID_BUILD_TOP}/bionic/${TZLOOKUP}
EOF
TZ_PREVIOUS_UPDATE_ZIP=update_${TZ_PREVIOUS}_test.zip
@@ -117,6 +119,7 @@
rules.version=${TZ_CURRENT}
bionic.file=${TMP_CURRENT}/tzdata
icu.file=${TMP_CURRENT}/icu_tzdata.dat
+tzlookup.file=${ANDROID_BUILD_TOP}/bionic/${TZLOOKUP}
EOF
TZ_CURRENT_UPDATE_ZIP=update_${TZ_CURRENT}_test.zip
@@ -146,6 +149,7 @@
rules.version=${TZ_NEXT}
bionic.file=${TMP_NEXT}/tzdata
icu.file=${TMP_NEXT}/icu_tzdata.dat
+tzlookup.file=${ANDROID_BUILD_TOP}/bionic/${TZLOOKUP}
EOF
TZ_NEXT_UPDATE_ZIP=update_${TZ_NEXT}_test.zip
diff --git a/tzdata/tools2/tzupdate.properties b/tzdata/tools2/tzupdate.properties
index 82fa2c4..264960b 100644
--- a/tzdata/tools2/tzupdate.properties
+++ b/tzdata/tools2/tzupdate.properties
@@ -8,4 +8,4 @@
bionic.file=
icu.file=
-
+tzlookup.file=
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
index bb49fe0..baadc85 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneDistroInstaller.java
@@ -23,7 +23,9 @@
import libcore.tzdata.shared2.DistroException;
import libcore.tzdata.shared2.DistroVersion;
import libcore.tzdata.shared2.FileUtils;
+import libcore.tzdata.shared2.StagedDistroOperation;
import libcore.tzdata.shared2.TimeZoneDistro;
+import libcore.util.TimeZoneFinder;
import libcore.util.ZoneInfoDB;
/**
@@ -31,38 +33,55 @@
* testing. This class is not thread-safe: callers are expected to handle mutual exclusion.
*/
public class TimeZoneDistroInstaller {
- /** {@link #installWithErrorCode(byte[])} result code: Success. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Success. */
public final static int INSTALL_SUCCESS = 0;
- /** {@link #installWithErrorCode(byte[])} result code: Distro corrupt. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro corrupt. */
public final static int INSTALL_FAIL_BAD_DISTRO_STRUCTURE = 1;
- /** {@link #installWithErrorCode(byte[])} result code: Distro version incompatible. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro version incompatible. */
public final static int INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION = 2;
- /** {@link #installWithErrorCode(byte[])} result code: Distro rules too old for device. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro rules too old for device. */
public final static int INSTALL_FAIL_RULES_TOO_OLD = 3;
- /** {@link #installWithErrorCode(byte[])} result code: Distro content failed validation. */
+ /** {@link #stageInstallWithErrorCode(byte[])} result code: Distro content failed validation. */
public final static int INSTALL_FAIL_VALIDATION_ERROR = 4;
+ // This constant must match one in system/core/tzdatacheck.cpp.
+ private static final String STAGED_TZ_DATA_DIR_NAME = "staged";
+ // This constant must match one in system/core/tzdatacheck.cpp.
private static final String CURRENT_TZ_DATA_DIR_NAME = "current";
private static final String WORKING_DIR_NAME = "working";
private static final String OLD_TZ_DATA_DIR_NAME = "old";
+ /**
+ * The name of the file in the staged directory used to indicate a staged uninstallation.
+ */
+ // This constant must match one in system/core/tzdatacheck.cpp.
+ // VisibleForTesting.
+ public static final String UNINSTALL_TOMBSTONE_FILE_NAME = "STAGED_UNINSTALL_TOMBSTONE";
+
private final String logTag;
private final File systemTzDataFile;
- private final File oldTzDataDir;
+ private final File oldStagedDataDir;
+ private final File stagedTzDataDir;
private final File currentTzDataDir;
private final File workingDir;
public TimeZoneDistroInstaller(String logTag, File systemTzDataFile, File installDir) {
this.logTag = logTag;
this.systemTzDataFile = systemTzDataFile;
- oldTzDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
+ oldStagedDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
+ stagedTzDataDir = new File(installDir, STAGED_TZ_DATA_DIR_NAME);
currentTzDataDir = new File(installDir, CURRENT_TZ_DATA_DIR_NAME);
workingDir = new File(installDir, WORKING_DIR_NAME);
}
// VisibleForTesting
- File getOldTzDataDir() {
- return oldTzDataDir;
+ File getOldStagedDataDir() {
+ return oldStagedDataDir;
+ }
+
+ // VisibleForTesting
+ File getStagedTzDataDir() {
+ return stagedTzDataDir;
}
// VisibleForTesting
@@ -76,34 +95,35 @@
}
/**
- * Install the supplied content.
+ * Stage an install of the supplied content, to be installed the next time the device boots.
*
- * <p>Errors during unpacking or installation will throw an {@link IOException}.
+ * <p>Errors during unpacking or staging will throw an {@link IOException}.
* If the distro content is invalid this method returns {@code false}.
* If the installation completed successfully this method returns {@code true}.
*/
public boolean install(byte[] content) throws IOException {
- int result = installWithErrorCode(content);
+ int result = stageInstallWithErrorCode(content);
return result == INSTALL_SUCCESS;
}
/**
- * Install the supplied time zone distro.
+ * Stage an install of the supplied content, to be installed the next time the device boots.
*
- * <p>Errors during unpacking or installation will throw an {@link IOException}.
+ * <p>Errors during unpacking or staging will throw an {@link IOException}.
* Returns {@link #INSTALL_SUCCESS} or an error code.
*/
- public int installWithErrorCode(byte[] content) throws IOException {
- if (oldTzDataDir.exists()) {
- FileUtils.deleteRecursive(oldTzDataDir);
+ public int stageInstallWithErrorCode(byte[] content) throws IOException {
+ if (oldStagedDataDir.exists()) {
+ FileUtils.deleteRecursive(oldStagedDataDir);
}
if (workingDir.exists()) {
FileUtils.deleteRecursive(workingDir);
}
Slog.i(logTag, "Unpacking / verifying time zone update");
- unpackDistro(content, workingDir);
try {
+ unpackDistro(content, workingDir);
+
DistroVersion distroVersion;
try {
distroVersion = readDistroVersion(workingDir);
@@ -131,6 +151,7 @@
return INSTALL_FAIL_RULES_TOO_OLD;
}
+ // Validate the tzdata file.
File zoneInfoFile = new File(workingDir, TimeZoneDistro.TZDATA_FILE_NAME);
ZoneInfoDB.TzData tzData = ZoneInfoDB.TzData.loadTzData(zoneInfoFile.getPath());
if (tzData == null) {
@@ -145,58 +166,100 @@
} finally {
tzData.close();
}
- // TODO(nfuller): Add deeper validity checks / canarying before applying.
+
+ // Validate the tzlookup.xml file.
+ File tzLookupFile = new File(workingDir, TimeZoneDistro.TZLOOKUP_FILE_NAME);
+ if (!tzLookupFile.exists()) {
+ Slog.i(logTag, "Update not applied: " + tzLookupFile + " does not exist");
+ return INSTALL_FAIL_BAD_DISTRO_STRUCTURE;
+ }
+ try {
+ TimeZoneFinder timeZoneFinder =
+ TimeZoneFinder.createInstance(tzLookupFile.getPath());
+ timeZoneFinder.validate();
+ } catch (IOException e) {
+ Slog.i(logTag, "Update not applied: " + tzLookupFile + " failed validation", e);
+ return INSTALL_FAIL_VALIDATION_ERROR;
+ }
+
+ // TODO(nfuller): Add validity checks for ICU data / canarying before applying.
// http://b/31008728
Slog.i(logTag, "Applying time zone update");
FileUtils.makeDirectoryWorldAccessible(workingDir);
- if (currentTzDataDir.exists()) {
- Slog.i(logTag, "Moving " + currentTzDataDir + " to " + oldTzDataDir);
- FileUtils.rename(currentTzDataDir, oldTzDataDir);
+ // Check if there is already a staged install or uninstall and remove it if there is.
+ if (!stagedTzDataDir.exists()) {
+ Slog.i(logTag, "Nothing to unstage at " + stagedTzDataDir);
+ } else {
+ Slog.i(logTag, "Moving " + stagedTzDataDir + " to " + oldStagedDataDir);
+ // Move stagedTzDataDir out of the way in one operation so we can't partially delete
+ // the contents.
+ FileUtils.rename(stagedTzDataDir, oldStagedDataDir);
}
- Slog.i(logTag, "Moving " + workingDir + " to " + currentTzDataDir);
- FileUtils.rename(workingDir, currentTzDataDir);
- Slog.i(logTag, "Update applied: " + currentTzDataDir + " successfully created");
+
+ // Move the workingDir to be the new staged directory.
+ Slog.i(logTag, "Moving " + workingDir + " to " + stagedTzDataDir);
+ FileUtils.rename(workingDir, stagedTzDataDir);
+ Slog.i(logTag, "Install staged: " + stagedTzDataDir + " successfully created");
return INSTALL_SUCCESS;
} finally {
- deleteBestEffort(oldTzDataDir);
+ deleteBestEffort(oldStagedDataDir);
deleteBestEffort(workingDir);
}
}
/**
- * Uninstall the current timezone update in /data, returning the device to using data from
- * /system. Returns {@code true} if uninstallation was successful, {@code false} if there was
- * nothing installed in /data to uninstall.
+ * Stage an uninstall of the current timezone update in /data which, on reboot, will return the
+ * device to using data from /system. Returns {@code true} if staging the uninstallation was
+ * successful, {@code false} if there was nothing installed in /data to uninstall. If there was
+ * something else staged it will be replaced by this call.
*
* <p>Errors encountered during uninstallation will throw an {@link IOException}.
*/
- public boolean uninstall() throws IOException {
+ public boolean stageUninstall() throws IOException {
Slog.i(logTag, "Uninstalling time zone update");
- // Make sure we don't have a dir where we're going to move the currently installed data to.
- if (oldTzDataDir.exists()) {
+ if (oldStagedDataDir.exists()) {
// If we can't remove this, an exception is thrown and we don't continue.
- FileUtils.deleteRecursive(oldTzDataDir);
+ FileUtils.deleteRecursive(oldStagedDataDir);
+ }
+ if (workingDir.exists()) {
+ FileUtils.deleteRecursive(workingDir);
}
- if (!currentTzDataDir.exists()) {
- Slog.i(logTag, "Nothing to uninstall at " + currentTzDataDir);
- return false;
+ try {
+ // Check if there is already an install or uninstall staged and remove it.
+ if (!stagedTzDataDir.exists()) {
+ Slog.i(logTag, "Nothing to unstage at " + stagedTzDataDir);
+ } else {
+ Slog.i(logTag, "Moving " + stagedTzDataDir + " to " + oldStagedDataDir);
+ // Move stagedTzDataDir out of the way in one operation so we can't partially delete
+ // the contents.
+ FileUtils.rename(stagedTzDataDir, oldStagedDataDir);
+ }
+
+ // If there's nothing actually installed, there's nothing to uninstall so no need to
+ // stage anything.
+ if (!currentTzDataDir.exists()) {
+ Slog.i(logTag, "Nothing to uninstall at " + currentTzDataDir);
+ return false;
+ }
+
+ // Stage an uninstall in workingDir.
+ FileUtils.ensureDirectoriesExist(workingDir, true /* makeWorldReadable */);
+ FileUtils.createEmptyFile(new File(workingDir, UNINSTALL_TOMBSTONE_FILE_NAME));
+
+ // Move the workingDir to be the new staged directory.
+ Slog.i(logTag, "Moving " + workingDir + " to " + stagedTzDataDir);
+ FileUtils.rename(workingDir, stagedTzDataDir);
+ Slog.i(logTag, "Uninstall staged: " + stagedTzDataDir + " successfully created");
+
+ return true;
+ } finally {
+ deleteBestEffort(oldStagedDataDir);
+ deleteBestEffort(workingDir);
}
-
- Slog.i(logTag, "Moving " + currentTzDataDir + " to " + oldTzDataDir);
- // Move currentTzDataDir out of the way in one operation so we can't partially delete
- // the contents, which would leave a partial install.
- FileUtils.rename(currentTzDataDir, oldTzDataDir);
-
- // Do our best to delete the now uninstalled timezone data.
- deleteBestEffort(oldTzDataDir);
-
- Slog.i(logTag, "Time zone update uninstalled.");
-
- return true;
}
/**
@@ -214,6 +277,24 @@
}
/**
+ * Reads information about any currently staged distro operation. Returns {@code null} if there
+ * is no distro operation staged.
+ *
+ * @throws IOException if there was a problem reading data from /data
+ * @throws DistroException if there was a problem with the staged distro format/structure
+ */
+ public StagedDistroOperation getStagedDistroOperation() throws DistroException, IOException {
+ if (!stagedTzDataDir.exists()) {
+ return null;
+ }
+ if (new File(stagedTzDataDir, UNINSTALL_TOMBSTONE_FILE_NAME).exists()) {
+ return StagedDistroOperation.uninstall();
+ } else {
+ return StagedDistroOperation.install(readDistroVersion(stagedTzDataDir));
+ }
+ }
+
+ /**
* Reads the timezone rules version present in /system. i.e. the version that would be present
* after a factory reset.
*
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
index 325b605..3af9f15 100644
--- a/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
+++ b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneDistroInstallerTest.java
@@ -24,7 +24,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Arrays;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@@ -32,10 +31,13 @@
import libcore.io.Streams;
import libcore.tzdata.shared2.DistroVersion;
import libcore.tzdata.shared2.FileUtils;
+import libcore.tzdata.shared2.StagedDistroOperation;
import libcore.tzdata.shared2.TimeZoneDistro;
import libcore.tzdata.testing.ZoneInfoTestHelper;
import libcore.tzdata.update2.tools.TimeZoneDistroBuilder;
+import static org.junit.Assert.assertArrayEquals;
+
/**
* Tests for {@link TimeZoneDistroInstaller}.
*/
@@ -90,100 +92,110 @@
}
/** Tests the an update on a device will fail if the /system tzdata file cannot be found. */
- public void testInstall_badSystemFile() throws Exception {
+ public void testStageInstallWithErrorCode_badSystemFile() throws Exception {
File doesNotExist = new File(testSystemTzDataDir, "doesNotExist");
TimeZoneDistroInstaller brokenSystemInstaller = new TimeZoneDistroInstaller(
"TimeZoneDistroInstallerTest", doesNotExist, testInstallDir);
TimeZoneDistro tzData = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
try {
- brokenSystemInstaller.installWithErrorCode(tzData.getBytes());
+ brokenSystemInstaller.stageInstallWithErrorCode(tzData.getBytes());
fail();
} catch (IOException expected) {}
- assertNoContentInstalled();
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/** Tests the first successful update on a device */
- public void testInstall_successfulFirstUpdate() throws Exception {
+ public void testStageInstallWithErrorCode_successfulFirstUpdate() throws Exception {
TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro.getBytes()));
- assertDistroInstalled(distro);
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertInstallDistroStaged(distro);
+ assertNoInstalledDistro();
}
/**
* Tests we can install an update the same version as is in /system.
*/
- public void testInstall_successfulFirstUpdate_sameVersionAsSystem() throws Exception {
+ public void testStageInstallWithErrorCode_successfulFirstUpdate_sameVersionAsSystem()
+ throws Exception {
TimeZoneDistro distro = createValidTimeZoneDistro(SYSTEM_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro.getBytes()));
- assertDistroInstalled(distro);
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertInstallDistroStaged(distro);
+ assertNoInstalledDistro();
}
/**
* Tests we cannot install an update older than the version in /system.
*/
- public void testInstall_unsuccessfulFirstUpdate_olderVersionThanSystem() throws Exception {
+ public void testStageInstallWithErrorCode_unsuccessfulFirstUpdate_olderVersionThanSystem()
+ throws Exception {
TimeZoneDistro distro = createValidTimeZoneDistro(OLDER_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
- * Tests an update on a device when there is a prior update already applied.
+ * Tests an update on a device when there is a prior update already staged.
*/
- public void testInstall_successfulFollowOnUpdate_newerVersion() throws Exception {
+ public void testStageInstallWithErrorCode_successfulFollowOnUpdate_newerVersion()
+ throws Exception {
TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro1.getBytes()));
- assertDistroInstalled(distro1);
+ installer.stageInstallWithErrorCode(distro1.getBytes()));
+ assertInstallDistroStaged(distro1);
TimeZoneDistro distro2 = createValidTimeZoneDistro(NEW_RULES_VERSION, 2);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro2.getBytes()));
- assertDistroInstalled(distro2);
+ installer.stageInstallWithErrorCode(distro2.getBytes()));
+ assertInstallDistroStaged(distro2);
TimeZoneDistro distro3 = createValidTimeZoneDistro(NEWER_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro3.getBytes()));
- assertDistroInstalled(distro3);
+ installer.stageInstallWithErrorCode(distro3.getBytes()));
+ assertInstallDistroStaged(distro3);
+ assertNoInstalledDistro();
}
/**
* Tests an update on a device when there is a prior update already applied, but the follow
* on update is older than in /system.
*/
- public void testInstall_unsuccessfulFollowOnUpdate_olderVersion() throws Exception {
+ public void testStageInstallWithErrorCode_unsuccessfulFollowOnUpdate_olderVersion()
+ throws Exception {
TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 2);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro1.getBytes()));
- assertDistroInstalled(distro1);
+ installer.stageInstallWithErrorCode(distro1.getBytes()));
+ assertInstallDistroStaged(distro1);
TimeZoneDistro distro2 = createValidTimeZoneDistro(OLDER_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD,
- installer.installWithErrorCode(distro2.getBytes()));
- assertDistroInstalled(distro1);
+ installer.stageInstallWithErrorCode(distro2.getBytes()));
+ assertInstallDistroStaged(distro1);
+ assertNoInstalledDistro();
}
- /** Tests that a distro with a missing file will not update the content. */
- public void testInstall_missingTzDataFile() throws Exception {
- TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ /** Tests that a distro with a missing tzdata file will not update the content. */
+ public void testStageInstallWithErrorCode_missingTzDataFile() throws Exception {
+ TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(installedDistro.getBytes()));
- assertDistroInstalled(installedDistro);
+ installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
TimeZoneDistro incompleteDistro =
createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
@@ -191,17 +203,18 @@
.buildUnvalidated();
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(incompleteDistro.getBytes()));
- assertDistroInstalled(installedDistro);
+ installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
+ assertNoInstalledDistro();
}
- /** Tests that a distro with a missing file will not update the content. */
- public void testInstall_missingIcuFile() throws Exception {
- TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ /** Tests that a distro with a missing ICU file will not update the content. */
+ public void testStageInstallWithErrorCode_missingIcuFile() throws Exception {
+ TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(installedDistro.getBytes()));
- assertDistroInstalled(installedDistro);
+ installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
TimeZoneDistro incompleteDistro =
createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
@@ -209,14 +222,53 @@
.buildUnvalidated();
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(incompleteDistro.getBytes()));
- assertDistroInstalled(installedDistro);
+ installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
+ assertNoInstalledDistro();
+ }
+
+ /** Tests that a distro with a missing tzlookup file will not update the content. */
+ public void testStageInstallWithErrorCode_missingTzLookupFile() throws Exception {
+ TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ assertEquals(
+ TimeZoneDistroInstaller.INSTALL_SUCCESS,
+ installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
+
+ TimeZoneDistro incompleteDistro =
+ createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
+ .setTzLookupXml(null)
+ .buildUnvalidated();
+ assertEquals(
+ TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
+ installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
+ assertNoInstalledDistro();
+ }
+
+ /** Tests that a distro with a bad tzlookup file will not update the content. */
+ public void testStageInstallWithErrorCode_badTzLookupFile() throws Exception {
+ TimeZoneDistro stagedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ assertEquals(
+ TimeZoneDistroInstaller.INSTALL_SUCCESS,
+ installer.stageInstallWithErrorCode(stagedDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
+
+ TimeZoneDistro incompleteDistro =
+ createValidTimeZoneDistroBuilder(NEWER_RULES_VERSION, 1)
+ .setTzLookupXml("<foo />")
+ .buildUnvalidated();
+ assertEquals(
+ TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR,
+ installer.stageInstallWithErrorCode(incompleteDistro.getBytes()));
+ assertInstallDistroStaged(stagedDistro);
+ assertNoInstalledDistro();
}
/**
* Tests that an update will be unpacked even if there is a partial update from a previous run.
*/
- public void testInstall_withWorkingDir() throws Exception {
+ public void testStageInstallWithErrorCode_withWorkingDir() throws Exception {
File workingDir = installer.getWorkingDir();
assertTrue(workingDir.mkdir());
createFile(new File(workingDir, "myFile"), new byte[] { 'a' });
@@ -224,42 +276,45 @@
TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
assertEquals(
TimeZoneDistroInstaller.INSTALL_SUCCESS,
- installer.installWithErrorCode(distro.getBytes()));
- assertDistroInstalled(distro);
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertInstallDistroStaged(distro);
+ assertNoInstalledDistro();
}
/**
* Tests that a distro without a distro version file will be rejected.
*/
- public void testInstall_withMissingDistroVersionFile() throws Exception {
+ public void testStageInstallWithErrorCode_withMissingDistroVersionFile() throws Exception {
// Create a distro without a version file.
TimeZoneDistro distro = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1)
.clearVersionForTests()
.buildUnvalidated();
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
* Tests that a distro with an newer distro version will be rejected.
*/
- public void testInstall_withNewerDistroVersion() throws Exception {
+ public void testStageInstallWithErrorCode_withNewerDistroVersion() throws Exception {
// Create a distro that will appear to be newer than the one currently supported.
TimeZoneDistro distro = createValidTimeZoneDistroBuilder(NEW_RULES_VERSION, 1)
.replaceFormatVersionForTests(2, 1)
.buildUnvalidated();
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
* Tests that a distro with a badly formed distro version will be rejected.
*/
- public void testInstall_withBadlyFormedDistroVersion() throws Exception {
+ public void testStageInstallWithErrorCode_withBadlyFormedDistroVersion() throws Exception {
// Create a distro that has an invalid major distro version. It should be 3 numeric
// characters, "." and 3 more numeric characters.
DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
@@ -269,14 +324,15 @@
TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidFormatVersionBytes);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
* Tests that a distro with a badly formed revision will be rejected.
*/
- public void testInstall_withBadlyFormedRevision() throws Exception {
+ public void testStageInstallWithErrorCode_withBadlyFormedRevision() throws Exception {
// Create a distro that has an invalid revision. It should be 3 numeric characters.
DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
byte[] invalidRevisionBytes = validDistroVersion.toBytes();
@@ -285,14 +341,15 @@
TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRevisionBytes);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
/**
* Tests that a distro with a badly formed rules version will be rejected.
*/
- public void testInstall_withBadlyFormedRulesVersion() throws Exception {
+ public void testStageInstallWithErrorCode_withBadlyFormedRulesVersion() throws Exception {
// Create a distro that has an invalid rules version. It should be in the form "2016c".
DistroVersion validDistroVersion = new DistroVersion(1, 1, NEW_RULES_VERSION, 1);
byte[] invalidRulesVersionBytes = validDistroVersion.toBytes();
@@ -301,38 +358,124 @@
TimeZoneDistro distro = createTimeZoneDistroWithVersionBytes(invalidRulesVersionBytes);
assertEquals(
TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE,
- installer.installWithErrorCode(distro.getBytes()));
- assertNoContentInstalled();
+ installer.stageInstallWithErrorCode(distro.getBytes()));
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
- public void testUninstall_noExistingDataDistro() throws Exception {
- assertFalse(installer.uninstall());
- assertNoContentInstalled();
+ public void testStageUninstall_noExistingDistro() throws Exception {
+ // To stage an uninstall, there would need to be installed rules.
+ assertFalse(installer.stageUninstall());
+
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
}
- public void testUninstall_existingDataDistro() throws Exception {
- File currentDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentDataDir.mkdir());
+ public void testStageUninstall_existingStagedDataDistro() throws Exception {
+ // To stage an uninstall, we need to have some installed rules.
+ TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ simulateInstalledDistro(installedDistro);
- assertTrue(installer.uninstall());
- assertNoContentInstalled();
+ File stagedDataDir = installer.getStagedTzDataDir();
+ assertTrue(stagedDataDir.mkdir());
+
+ assertTrue(installer.stageUninstall());
+ assertDistroUninstallStaged();
+ assertInstalledDistro(installedDistro);
}
- public void testUninstall_oldDirsAlreadyExists() throws Exception {
- File oldTzDataDir = installer.getOldTzDataDir();
- assertTrue(oldTzDataDir.mkdir());
+ public void testStageUninstall_oldDirsAlreadyExists() throws Exception {
+ // To stage an uninstall, we need to have some installed rules.
+ TimeZoneDistro installedDistro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ simulateInstalledDistro(installedDistro);
- File currentDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentDataDir.mkdir());
+ File oldStagedDataDir = installer.getOldStagedDataDir();
+ assertTrue(oldStagedDataDir.mkdir());
- assertTrue(installer.uninstall());
- assertNoContentInstalled();
+ File workingDir = installer.getWorkingDir();
+ assertTrue(workingDir.mkdir());
+
+ assertTrue(installer.stageUninstall());
+
+ assertDistroUninstallStaged();
+ assertFalse(workingDir.exists());
+ assertFalse(oldStagedDataDir.exists());
+ assertInstalledDistro(installedDistro);
}
public void testGetSystemRulesVersion() throws Exception {
assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion());
}
+ public void testGetInstalledDistroVersion() throws Exception {
+ // Check result when nothing installed.
+ assertNull(installer.getInstalledDistroVersion());
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
+
+ // Now simulate there being an existing install active.
+ TimeZoneDistro distro = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ simulateInstalledDistro(distro);
+ assertInstalledDistro(distro);
+
+ // Check result when something installed.
+ assertEquals(distro.getDistroVersion(), installer.getInstalledDistroVersion());
+ assertNoDistroOperationStaged();
+ assertInstalledDistro(distro);
+ }
+
+ public void testGetStagedDistroOperation() throws Exception {
+ TimeZoneDistro distro1 = createValidTimeZoneDistro(NEW_RULES_VERSION, 1);
+ TimeZoneDistro distro2 = createValidTimeZoneDistro(NEWER_RULES_VERSION, 1);
+
+ // Check result when nothing staged.
+ assertNull(installer.getStagedDistroOperation());
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
+
+ // Check result after unsuccessfully staging an uninstall.
+ // Can't stage an uninstall without an installed distro.
+ assertFalse(installer.stageUninstall());
+ assertNull(installer.getStagedDistroOperation());
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
+
+ // Check result after staging an install.
+ assertTrue(installer.install(distro1.getBytes()));
+ StagedDistroOperation expectedStagedInstall =
+ StagedDistroOperation.install(distro1.getDistroVersion());
+ assertEquals(expectedStagedInstall, installer.getStagedDistroOperation());
+ assertInstallDistroStaged(distro1);
+ assertNoInstalledDistro();
+
+ // Check result after unsuccessfully staging an uninstall (but after removing a staged
+ // install). Can't stage an uninstall without an installed distro.
+ assertFalse(installer.stageUninstall());
+ assertNull(installer.getStagedDistroOperation());
+ assertNoDistroOperationStaged();
+ assertNoInstalledDistro();
+
+ // Now simulate there being an existing install active.
+ simulateInstalledDistro(distro1);
+ assertInstalledDistro(distro1);
+
+ // Check state after successfully staging an uninstall.
+ assertTrue(installer.stageUninstall());
+ StagedDistroOperation expectedStagedUninstall = StagedDistroOperation.uninstall();
+ assertEquals(expectedStagedUninstall, installer.getStagedDistroOperation());
+ assertDistroUninstallStaged();
+ assertInstalledDistro(distro1);
+
+ // Check state after successfully staging an install.
+ assertEquals(TimeZoneDistroInstaller.INSTALL_SUCCESS,
+ installer.stageInstallWithErrorCode(distro2.getBytes()));
+ StagedDistroOperation expectedStagedInstall2 =
+ StagedDistroOperation.install(distro2.getDistroVersion());
+ assertEquals(expectedStagedInstall2, installer.getStagedDistroOperation());
+ assertInstallDistroStaged(distro2);
+ assertInstalledDistro(distro1);
+ }
+
private static TimeZoneDistro createValidTimeZoneDistro(
String rulesVersion, int revision) throws Exception {
return createValidTimeZoneDistroBuilder(rulesVersion, revision).build();
@@ -343,6 +486,17 @@
byte[] bionicTzData = createTzData(rulesVersion);
byte[] icuData = new byte[] { 'a' };
+ String tzlookupXml = "<timezones>\n"
+ + " <countryzones>\n"
+ + " <country code=\"us\">\n"
+ + " <id>America/New_York\"</id>\n"
+ + " <id>America/Los_Angeles</id>\n"
+ + " </country>\n"
+ + " <country code=\"gb\">\n"
+ + " <id>Europe/London</id>\n"
+ + " </country>\n"
+ + " </countryzones>\n"
+ + "</timezones>\n";
DistroVersion distroVersion = new DistroVersion(
DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
DistroVersion.CURRENT_FORMAT_MINOR_VERSION,
@@ -350,28 +504,35 @@
revision);
return new TimeZoneDistroBuilder()
.setDistroVersion(distroVersion)
- .setTzData(bionicTzData)
- .setIcuData(icuData);
+ .setTzDataFile(bionicTzData)
+ .setIcuDataFile(icuData)
+ .setTzLookupXml(tzlookupXml);
}
- private void assertDistroInstalled(TimeZoneDistro expectedDistro) throws Exception {
+ private void assertInstallDistroStaged(TimeZoneDistro expectedDistro) throws Exception {
assertTrue(testInstallDir.exists());
- File currentTzDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentTzDataDir.exists());
+ File stagedTzDataDir = installer.getStagedTzDataDir();
+ assertTrue(stagedTzDataDir.exists());
File distroVersionFile =
- new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
+ new File(stagedTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
assertTrue(distroVersionFile.exists());
- File bionicFile = new File(currentTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME);
+ File bionicFile = new File(stagedTzDataDir, TimeZoneDistro.TZDATA_FILE_NAME);
assertTrue(bionicFile.exists());
- File icuFile = new File(currentTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME);
+ File icuFile = new File(stagedTzDataDir, TimeZoneDistro.ICU_DATA_FILE_NAME);
assertTrue(icuFile.exists());
- // Assert getInstalledDistroVersion() is reporting correctly.
- assertEquals(expectedDistro.getDistroVersion(), installer.getInstalledDistroVersion());
+ File tzLookupFile = new File(stagedTzDataDir, TimeZoneDistro.TZLOOKUP_FILE_NAME);
+ assertTrue(tzLookupFile.exists());
+
+ // Assert getStagedDistroState() is reporting correctly.
+ StagedDistroOperation stagedDistroOperation = installer.getStagedDistroOperation();
+ assertNotNull(stagedDistroOperation);
+ assertFalse(stagedDistroOperation.isUninstall);
+ assertEquals(expectedDistro.getDistroVersion(), stagedDistroOperation.distroVersion);
try (ZipInputStream zis = new ZipInputStream(
new ByteArrayInputStream(expectedDistro.getBytes()))) {
@@ -385,6 +546,8 @@
actualFile = icuFile;
} else if (entryName.endsWith(TimeZoneDistro.TZDATA_FILE_NAME)) {
actualFile = bionicFile;
+ } else if (entryName.endsWith(TimeZoneDistro.TZLOOKUP_FILE_NAME)) {
+ actualFile = tzLookupFile;
} else {
throw new AssertionFailedError("Unknown file found");
}
@@ -401,23 +564,64 @@
throws Exception {
byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath());
byte[] expectedBytes = Streams.readFullyNoClose(expected);
- assertTrue(Arrays.equals(expectedBytes, actualBytes));
+ assertArrayEquals(expectedBytes, actualBytes);
}
- private void assertNoContentInstalled() throws Exception {
- assertNull(installer.getInstalledDistroVersion());
+ private void assertNoDistroOperationStaged() throws Exception {
+ assertNull(installer.getStagedDistroOperation());
- File currentTzDataDir = installer.getCurrentTzDataDir();
- assertFalse(currentTzDataDir.exists());
+ File stagedTzDataDir = installer.getStagedTzDataDir();
+ assertFalse(stagedTzDataDir.exists());
// Also check no working directories are left lying around.
File workingDir = installer.getWorkingDir();
assertFalse(workingDir.exists());
- File oldDataDir = installer.getOldTzDataDir();
+ File oldDataDir = installer.getOldStagedDataDir();
assertFalse(oldDataDir.exists());
}
+ private void assertDistroUninstallStaged() throws Exception {
+ assertEquals(StagedDistroOperation.uninstall(), installer.getStagedDistroOperation());
+
+ File stagedTzDataDir = installer.getStagedTzDataDir();
+ assertTrue(stagedTzDataDir.exists());
+ assertTrue(stagedTzDataDir.isDirectory());
+
+ File uninstallTombstone =
+ new File(stagedTzDataDir, TimeZoneDistroInstaller.UNINSTALL_TOMBSTONE_FILE_NAME);
+ assertTrue(uninstallTombstone.exists());
+ assertTrue(uninstallTombstone.isFile());
+
+ // Also check no working directories are left lying around.
+ File workingDir = installer.getWorkingDir();
+ assertFalse(workingDir.exists());
+
+ File oldDataDir = installer.getOldStagedDataDir();
+ assertFalse(oldDataDir.exists());
+ }
+
+ private void simulateInstalledDistro(TimeZoneDistro timeZoneDistro) throws Exception {
+ File currentTzDataDir = installer.getCurrentTzDataDir();
+ assertFalse(currentTzDataDir.exists());
+ assertTrue(currentTzDataDir.mkdir());
+ timeZoneDistro.extractTo(currentTzDataDir);
+ }
+
+ private void assertNoInstalledDistro() {
+ assertFalse(installer.getCurrentTzDataDir().exists());
+ }
+
+ private void assertInstalledDistro(TimeZoneDistro timeZoneDistro) throws Exception {
+ File currentTzDataDir = installer.getCurrentTzDataDir();
+ assertTrue(currentTzDataDir.exists());
+ File versionFile = new File(currentTzDataDir, TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
+ assertTrue(versionFile.exists());
+ byte[] expectedVersionBytes = timeZoneDistro.getDistroVersion().toBytes();
+ byte[] actualVersionBytes = FileUtils.readBytes(versionFile, expectedVersionBytes.length);
+ assertArrayEquals(expectedVersionBytes, actualVersionBytes);
+ }
+
private static byte[] createTzData(String rulesVersion) {
return new ZoneInfoTestHelper.TzDataBuilder()
.initializeToValid()