Merge "Regex support for named capture groups."
diff --git a/NativeCode.mk b/NativeCode.mk
index fcf4c24..d8e9d64 100644
--- a/NativeCode.mk
+++ b/NativeCode.mk
@@ -179,7 +179,9 @@
# Set of gtest unit tests.
include $(CLEAR_VARS)
-LOCAL_CFLAGS += $(libart_cflags)
+# Add -fno-builtin so that the compiler doesn't attempt to inline
+# memcpy calls that are not really aligned.
+LOCAL_CFLAGS += $(libart_cflags) -fno-builtin
LOCAL_CPPFLAGS += $(core_cppflags)
LOCAL_SRC_FILES += \
luni/src/test/native/libcore_io_Memory_test.cpp \
diff --git a/dalvik/src/main/java/dalvik/system/DexFile.java b/dalvik/src/main/java/dalvik/system/DexFile.java
index e89386a..42cdac0 100644
--- a/dalvik/src/main/java/dalvik/system/DexFile.java
+++ b/dalvik/src/main/java/dalvik/system/DexFile.java
@@ -146,6 +146,7 @@
}
mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
+ mInternalCookie = mCookie;
mFileName = sourceName;
//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}
@@ -451,8 +452,7 @@
/**
* dex2oat should be run to update the apk/jar because the existing code
- * is not relocated to match the boot image and does not have the
- * necessary patch information to use patchoat.
+ * is not relocated to match the boot image.
*
* See {@link #getDexOptNeeded(String, String, int)}.
*
@@ -461,15 +461,6 @@
public static final int DEX2OAT_FOR_RELOCATION = 4;
/**
- * patchoat should be run to update the apk/jar.
- *
- * See {@link #getDexOptNeeded(String, String, int)}.
- *
- * @hide
- */
- public static final int PATCHOAT_FOR_RELOCATION = 5;
-
- /**
* Returns the VM's opinion of what kind of dexopt is needed to make the
* apk/jar file up to date, where {@code targetMode} is used to indicate what
* type of compilation the caller considers up-to-date, and {@code newProfile}
@@ -480,7 +471,7 @@
* @param newProfile flag that describes whether a profile corresponding
* to the dex file has been recently updated and should be considered
* in the state of the file.
- * @return NO_DEXOPT_NEEDED, DEX2OAT_*, or PATCHOAT_*. See documentation
+ * @return NO_DEXOPT_NEEDED, or DEX2OAT_*. See documentation
* of the particular status code for more information on its
* meaning. Returns a positive status code if the status refers to
* the oat file in the oat location. Returns a negative status
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/ByteBufferTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/ByteBufferTest.java
index b25c4de..db09af8 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/ByteBufferTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/nio/ByteBufferTest.java
@@ -17,6 +17,7 @@
package org.apache.harmony.tests.java.nio;
+import java.io.RandomAccessFile;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
@@ -29,6 +30,9 @@
import java.nio.LongBuffer;
import java.nio.ReadOnlyBufferException;
import java.nio.ShortBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Arrays;
/**
@@ -2033,6 +2037,28 @@
}
}
+ // http://b/34045479
+ public void testMappedByteBuffer_Put_ReadOnlyHeapByteBuffer() throws Exception {
+ // Create a temp file
+ byte[] data = new byte[] {1, 2, 3, 4};
+ Path tempFile = Files.createTempFile("mmap", "test");
+ Files.write(tempFile, data);
+
+ // Create a read-only heap buffer
+ ByteBuffer readOnlySource = ByteBuffer.allocate(4).asReadOnlyBuffer();
+ try (RandomAccessFile tempRAF = new RandomAccessFile(tempFile.toFile(), "rw")) {
+ FileChannel tempFileChannel = tempRAF.getChannel();
+ ByteBuffer mappedByteBuffer =
+ tempFileChannel.map(FileChannel.MapMode.READ_WRITE, 0, tempFileChannel.size());
+
+ // Try to put a non-empty, read-only heap byte buffer into a mapped byte buffer.
+ mappedByteBuffer.put(readOnlySource);
+ tempFileChannel.close();
+ } finally {
+ Files.delete(tempFile);
+ }
+ }
+
private void loadTestData1(byte array[], int offset, int length) {
for (int i = 0; i < length; i++) {
array[offset + i] = (byte) i;
diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java
index cedbf82..fb348bc 100644
--- a/luni/src/main/java/libcore/io/IoBridge.java
+++ b/luni/src/main/java/libcore/io/IoBridge.java
@@ -97,12 +97,7 @@
try {
Libcore.os.bind(fd, address, port);
} catch (ErrnoException errnoException) {
- if (errnoException.errno == EADDRINUSE || errnoException.errno == EADDRNOTAVAIL ||
- errnoException.errno == EPERM || errnoException.errno == EACCES) {
- throw new BindException(errnoException.getMessage(), errnoException);
- } else {
- throw new SocketException(errnoException.getMessage(), errnoException);
- }
+ throw new BindException(errnoException.getMessage(), errnoException);
}
}
@@ -567,11 +562,10 @@
return result;
}
- private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException)
- throws IOException {
+ private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException) throws SocketException {
if (isDatagram) {
- if (errnoException.errno == ECONNREFUSED) {
- throw new PortUnreachableException("ICMP Port Unreachable");
+ if (errnoException.errno == ECONNRESET || errnoException.errno == ECONNREFUSED) {
+ return 0;
}
} else {
if (errnoException.errno == EAGAIN) {
@@ -580,7 +574,7 @@
return 0;
}
}
- throw errnoException.rethrowAsIOException();
+ throw errnoException.rethrowAsSocketException();
}
public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
@@ -613,12 +607,8 @@
}
if (packet != null) {
packet.setReceivedLength(byteCount);
+ packet.setAddress(srcAddress.getAddress());
packet.setPort(srcAddress.getPort());
-
- // packet.address should only be changed when it is different from srcAddress.
- if (!srcAddress.getAddress().equals(packet.getAddress())) {
- packet.setAddress(srcAddress.getAddress());
- }
}
return byteCount;
}
@@ -632,7 +622,7 @@
}
} else {
if (isConnected && errnoException.errno == ECONNREFUSED) {
- throw new PortUnreachableException("ICMP Port Unreachable", errnoException);
+ throw new PortUnreachableException("", errnoException);
} else if (errnoException.errno == EAGAIN) {
throw new SocketTimeoutException(errnoException);
} else {
diff --git a/luni/src/main/native/ExecStrings.cpp b/luni/src/main/native/ExecStrings.cpp
index 4f90431..9a408fd 100644
--- a/luni/src/main/native/ExecStrings.cpp
+++ b/luni/src/main/native/ExecStrings.cpp
@@ -20,7 +20,7 @@
#include <stdlib.h>
-#include <android/log.h>
+#include <log/log.h>
#include "ScopedLocalRef.h"
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index ec9d878..3c244ef 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -18,7 +18,7 @@
#include <stdlib.h>
-#include "android/log.h"
+#include "log/log.h"
#include "JniConstants.h"
#include "ScopedLocalFrame.h"
diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp
index 7456028..46594c6 100644
--- a/luni/src/main/native/libcore_icu_ICU.cpp
+++ b/luni/src/main/native/libcore_icu_ICU.cpp
@@ -31,8 +31,8 @@
#include <memory>
#include <vector>
-#include <android/log.h>
#include <android-base/unique_fd.h>
+#include <log/log.h>
#include "IcuUtilities.h"
#include "JNIHelp.h"
diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp
index 86c9782..d150052 100644
--- a/luni/src/main/native/libcore_io_Posix.cpp
+++ b/luni/src/main/native/libcore_io_Posix.cpp
@@ -47,9 +47,9 @@
#include <memory>
-#include <android/log.h>
#include <android-base/file.h>
#include <android-base/strings.h>
+#include <log/log.h>
#include "AsynchronousCloseMonitor.h"
#include "ExecStrings.h"
@@ -1718,8 +1718,13 @@
sockaddr* from = (javaInetSocketAddress != NULL) ? reinterpret_cast<sockaddr*>(&ss) : NULL;
socklen_t* fromLength = (javaInetSocketAddress != NULL) ? &sl : 0;
jint recvCount = NET_FAILURE_RETRY(env, ssize_t, recvfrom, javaFd, bytes.get() + byteOffset, byteCount, flags, from, fromLength);
- if (recvCount > 0) {
- fillInetSocketAddress(env, javaInetSocketAddress, ss);
+ if (recvCount >= 0) {
+ // The socket may have performed orderly shutdown and recvCount would return 0 (see man 2
+ // recvfrom), in which case ss.ss_family == AF_UNIX and fillInetSocketAddress would fail.
+ // Don't fill in the address if recvfrom didn't succeed. http://b/33483694
+ if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) {
+ fillInetSocketAddress(env, javaInetSocketAddress, ss);
+ }
}
return recvCount;
}
diff --git a/luni/src/test/java/libcore/io/OsTest.java b/luni/src/test/java/libcore/io/OsTest.java
index bc28121..7bbf383 100644
--- a/luni/src/test/java/libcore/io/OsTest.java
+++ b/luni/src/test/java/libcore/io/OsTest.java
@@ -28,6 +28,8 @@
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -43,6 +45,7 @@
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;
import junit.framework.TestCase;
+
import static android.system.OsConstants.*;
public class OsTest extends TestCase {
@@ -762,4 +765,20 @@
assertTrue("Could not delete symlink: " + path, new File(path).delete());
}
}
+
+ // Address should be correctly set for empty packets. http://b/33481605
+ public void test_recvfrom_EmptyPacket() throws Exception {
+ try (DatagramSocket ds = new DatagramSocket();
+ DatagramSocket srcSock = new DatagramSocket()) {
+ srcSock.send(new DatagramPacket(new byte[0], 0, ds.getLocalSocketAddress()));
+
+ byte[] recvBuf = new byte[16];
+ InetSocketAddress address = new InetSocketAddress();
+ int recvCount =
+ android.system.Os.recvfrom(ds.getFileDescriptor$(), recvBuf, 0, 16, 0, address);
+ assertEquals(0, recvCount);
+ assertTrue(address.getAddress().isLoopbackAddress());
+ assertEquals(srcSock.getLocalPort(), address.getPort());
+ }
+ }
}
diff --git a/luni/src/test/java/libcore/java/nio/charset/OldCharset_AbstractTest.java b/luni/src/test/java/libcore/java/nio/charset/OldCharset_AbstractTest.java
index d4cf83e..60c716c 100644
--- a/luni/src/test/java/libcore/java/nio/charset/OldCharset_AbstractTest.java
+++ b/luni/src/test/java/libcore/java/nio/charset/OldCharset_AbstractTest.java
@@ -177,14 +177,22 @@
encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
decoder.onMalformedInput(CodingErrorAction.REPORT);
CharBuffer inputCB = CharBuffer.allocate(65536);
- for (int code = 32; code <= 65533; ++code) {
- // icu4c seems to accept any surrogate as a sign that "more is coming",
- // even for charsets like US-ASCII. http://b/10310751
- if (code >= 0xd800 && code <= 0xdfff) {
+ // Only test most of the Unicode BMP.
+ // Supplementary code points would require use of encoder.canEncode(CharSequence).
+ for (char code = 0x20; code <= 0xfffd; code++) {
+ // Skip surrogates to avoid writing broken UTF-16.
+ // Ignore charsets that do convert surrogate code units.
+ if (code == 0xd800) {
+ code = 0xdfff;
continue;
}
- if (encoder.canEncode((char) code)) {
- inputCB.put((char) code);
+ // Ignore the private use area.
+ if (code == 0xe000) {
+ code = 0xf8ff;
+ continue;
+ }
+ if (encoder.canEncode(code)) {
+ inputCB.put(code);
}
}
inputCB.rewind();
diff --git a/luni/src/test/java/libcore/java/util/TreeMapTest.java b/luni/src/test/java/libcore/java/util/TreeMapTest.java
index bd31a61..9384d56 100644
--- a/luni/src/test/java/libcore/java/util/TreeMapTest.java
+++ b/luni/src/test/java/libcore/java/util/TreeMapTest.java
@@ -612,6 +612,33 @@
return bound == '[' || bound == '(';
}
+ // http://b//26336181
+ //
+ // Note that this is only worth working around because these bogus comparators worked
+ // somewhat-fine on M and below provided that :
+ //
+ // (1) put was called with distinct elements (i.e, with no two elements equal() to each other)
+ // (2) get or get-like methods are never called
+ //
+ // These comparators are clearly bogus but are somewhat common.
+ public void testTreeMapWithBogusComparator() {
+ TreeMap<String, String> treeMap = new TreeMap<String, String>(
+ new Comparator<String>() {
+ @Override
+ public int compare(String o1, String o2) {
+ if (o1.equals(o2)) {
+ throw new IllegalArgumentException("Expected unequal elements");
+ }
+
+ return o1.compareTo(o2);
+ }
+ }
+ );
+
+ treeMap.put("candy", "floss");
+ treeMap.put("cheddar", "cheese");
+ }
+
public void test_spliterator_keySet() {
TreeMap<String, String> treeMap = new TreeMap<>();
treeMap.put("a", "1");
diff --git a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
index 99691d5..901519e 100644
--- a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
+++ b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java
@@ -110,8 +110,8 @@
}
public void testLoadTzData_invalidOffsets() throws Exception {
- ZoneInfoTestHelper.TzDataBuilder builder
- = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+ ZoneInfoTestHelper.TzDataBuilder builder =
+ new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
// Sections must be in the correct order: section sizing is calculated using them.
builder.setIndexOffsetOverride(10);
@@ -124,8 +124,9 @@
}
public void testLoadTzData_zoneTabOutsideFile() throws Exception {
- ZoneInfoTestHelper.TzDataBuilder builder
- = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+ ZoneInfoTestHelper.TzDataBuilder builder =
+ new ZoneInfoTestHelper.TzDataBuilder()
+ .initializeToValid();
// Sections must be in the correct order: section sizing is calculated using them.
builder.setIndexOffsetOverride(10);
@@ -139,8 +140,8 @@
}
public void testLoadTzData_nonDivisibleIndex() throws Exception {
- ZoneInfoTestHelper.TzDataBuilder builder
- = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+ ZoneInfoTestHelper.TzDataBuilder builder =
+ new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
// Sections must be in the correct order: section sizing is calculated using them.
int indexOffset = 10;
@@ -156,22 +157,22 @@
}
public void testLoadTzData_badId() throws Exception {
- ZoneInfoTestHelper.TzDataBuilder builder
- = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+ ZoneInfoTestHelper.TzDataBuilder builder =
+ new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
builder.clearZicData();
byte[] validZicData =
- new ZoneInfoTestHelper.ZoneInfoDataBuilder().initializeToValid().build();
+ new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
builder.addZicData("", validZicData); // "" is an invalid ID
checkInvalidDataDetected(builder.build());
}
public void testLoadTzData_badIdOrder() throws Exception {
- ZoneInfoTestHelper.TzDataBuilder builder
- = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+ ZoneInfoTestHelper.TzDataBuilder builder =
+ new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
builder.clearZicData();
byte[] validZicData =
- new ZoneInfoTestHelper.ZoneInfoDataBuilder().initializeToValid().build();
+ new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
builder.addZicData("Europe/Zurich", validZicData);
builder.addZicData("Europe/London", validZicData);
@@ -179,11 +180,11 @@
}
public void testLoadTzData_duplicateId() throws Exception {
- ZoneInfoTestHelper.TzDataBuilder builder
- = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+ ZoneInfoTestHelper.TzDataBuilder builder =
+ new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
builder.clearZicData();
byte[] validZicData =
- new ZoneInfoTestHelper.ZoneInfoDataBuilder().initializeToValid().build();
+ new ZoneInfoTestHelper.ZicDataBuilder().initializeToValid().build();
builder.addZicData("Europe/London", validZicData);
builder.addZicData("Europe/London", validZicData);
@@ -191,8 +192,8 @@
}
public void testLoadTzData_badZicLength() throws Exception {
- ZoneInfoTestHelper.TzDataBuilder builder
- = new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
+ ZoneInfoTestHelper.TzDataBuilder builder =
+ new ZoneInfoTestHelper.TzDataBuilder().initializeToValid();
builder.clearZicData();
byte[] invalidZicData = "This is too short".getBytes();
builder.addZicData("Europe/London", invalidZicData);
diff --git a/luni/src/test/java/libcore/util/ZoneInfoTest.java b/luni/src/test/java/libcore/util/ZoneInfoTest.java
index d2cd1fb..82418ca 100644
--- a/luni/src/test/java/libcore/util/ZoneInfoTest.java
+++ b/luni/src/test/java/libcore/util/ZoneInfoTest.java
@@ -351,15 +351,17 @@
}
public void testReadTimeZone_valid() throws Exception {
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.initializeToValid();
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid();
assertNotNull(createZoneInfo(getName(), System.currentTimeMillis(), builder.build()));
}
public void testReadTimeZone_badMagic() throws Exception {
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.initializeToValid();
- builder.setMagic(0xdeadbeef); // Bad magic.
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid()
+ .setMagic(0xdeadbeef); // Bad magic.
try {
createZoneInfo(getName(), System.currentTimeMillis(), builder.build());
fail();
@@ -370,9 +372,10 @@
* Checks to make sure that ZoneInfo rejects more than 256 types.
*/
public void testReadTimeZone_TooManyTypes() throws Exception {
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.initializeToValid();
- builder.setTypeCountOverride(257);
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid()
+ .setTypeCountOverride(257);
byte[] bytes = builder.build();
try {
createZoneInfo(getName(), System.currentTimeMillis(), bytes);
@@ -385,9 +388,10 @@
* Checks to make sure that ZoneInfo rejects more than 2000 transitions.
*/
public void testReadTimeZone_TooManyTransitions() throws Exception {
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.initializeToValid();
- builder.setTransitionCountOverride(2001);
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid()
+ .setTransitionCountOverride(2001);
byte[] bytes = builder.build();
try {
createZoneInfo(getName(), System.currentTimeMillis(), bytes);
@@ -400,9 +404,10 @@
* Checks to make sure that ZoneInfo rejects a negative type count.
*/
public void testReadTimeZone_NegativeTypes() throws Exception {
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.initializeToValid();
- builder.setTypeCountOverride(-1);
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid()
+ .setTypeCountOverride(-1);
byte[] bytes = builder.build();
try {
createZoneInfo(getName(), System.currentTimeMillis(), bytes);
@@ -415,9 +420,10 @@
* Checks to make sure that ZoneInfo rejects a negative transition count.
*/
public void testReadTimeZone_NegativeTransitions() throws Exception {
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.initializeToValid();
- builder.setTransitionCountOverride(-1);
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid()
+ .setTransitionCountOverride(-1);
byte[] bytes = builder.build();
try {
createZoneInfo(getName(), System.currentTimeMillis(), bytes);
@@ -437,8 +443,10 @@
{ 1800, 1 },
};
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.setTransitionsAndTypes(transitions, types);
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid()
+ .setTransitionsAndTypes(transitions, types);
byte[] bytes = builder.build();
try {
@@ -459,8 +467,10 @@
{ 1800, 1 },
};
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.setTransitionsAndTypes(transitions, types);
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid()
+ .setTransitionsAndTypes(transitions, types);
byte[] bytes = builder.build();
try {
@@ -481,8 +491,10 @@
{ 1800, 2 }, // Invalid isDst - must be 0 or 1
};
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.setTransitionsAndTypes(transitions, types);
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .initializeToValid()
+ .setTransitionsAndTypes(transitions, types);
byte[] bytes = builder.build();
try {
@@ -548,8 +560,9 @@
private ZoneInfo createZoneInfo(String name, int[][] transitions, int[][] types,
long currentTimeMillis) throws Exception {
- ZoneInfoTestHelper.ZoneInfoDataBuilder builder = new ZoneInfoTestHelper.ZoneInfoDataBuilder();
- builder.setTransitionsAndTypes(transitions, types);
+ ZoneInfoTestHelper.ZicDataBuilder builder =
+ new ZoneInfoTestHelper.ZicDataBuilder()
+ .setTransitionsAndTypes(transitions, types);
return createZoneInfo(name, currentTimeMillis, builder.build());
}
diff --git a/ojluni/src/main/java/java/io/Console.java b/ojluni/src/main/java/java/io/Console.java
index 3cab03e..2b4e4e6 100644
--- a/ojluni/src/main/java/java/io/Console.java
+++ b/ojluni/src/main/java/java/io/Console.java
@@ -559,19 +559,4 @@
cs));
rcb = new char[1024];
}
-
- /**
- * Android-changed: Added method for internal use only, and also in use
- * by tests.
- *
- * @hide
- */
- public static synchronized Console getConsole() {
- if (istty()) {
- if (cons == null)
- cons = new Console();
- return cons;
- }
- return null;
- }
}
diff --git a/ojluni/src/main/java/java/lang/Math.java b/ojluni/src/main/java/java/lang/Math.java
index abcb6bb..b53d35c 100644
--- a/ojluni/src/main/java/java/lang/Math.java
+++ b/ojluni/src/main/java/java/lang/Math.java
@@ -1203,8 +1203,9 @@
* @return the absolute value of the argument.
*/
public static float abs(float a) {
- // Note, as a "quality of implementation" rather than pure "spec compliance" we require that
- // Math.abs() clears the sign bit (but changes nothing else) for all numbers, including NaN.
+ // Note, as a "quality of implementation", rather than pure "spec compliance",
+ // we require that Math.abs() clears the sign bit (but changes nothing else)
+ // for all numbers, including NaN (signaling NaN may become quiet though).
return Float.intBitsToFloat(0x7fffffff & Float.floatToRawIntBits(a));
}
@@ -1224,8 +1225,9 @@
* @return the absolute value of the argument.
*/
public static double abs(double a) {
- // Note, as a "quality of implementation" rather than pure "spec compliance" we require that
- // Math.abs() clears the sign bit (but changes nothing else) for all numbers, including NaN.
+ // Note, as a "quality of implementation", rather than pure "spec compliance",
+ // we require that Math.abs() clears the sign bit (but changes nothing else)
+ // for all numbers, including NaN (signaling NaN may become quiet though).
return Double.longBitsToDouble(0x7fffffffffffffffL & Double.doubleToRawLongBits(a));
}
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandleInfo.java b/ojluni/src/main/java/java/lang/invoke/MethodHandleInfo.java
index 08ce6f0..b821f63 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandleInfo.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandleInfo.java
@@ -27,12 +27,13 @@
import java.lang.reflect.*;
import java.util.*;
+import java.lang.invoke.MethodHandles.Lookup;
import static java.lang.invoke.MethodHandleStatics.*;
/**
* A symbolic reference obtained by cracking a direct method handle
* into its consitutent symbolic parts.
- * To crack a direct method handle, call {@code Lookup#revealDirect Lookup.revealDirect}.
+ * To crack a direct method handle, call {@link Lookup#revealDirect Lookup.revealDirect}.
* <h1><a name="directmh"></a>Direct Method Handles</h1>
* A <em>direct method handle</em> represents a method, constructor, or field without
* any intervening argument bindings or other transformations.
@@ -43,16 +44,16 @@
* <li>By executing an {@code ldc} instruction on a {@code CONSTANT_MethodHandle} constant.
* (See the Java Virtual Machine Specification, sections 4.4.8 and 5.4.3.)
* <li>By calling one of the <a href="MethodHandles.Lookup.html#lookups">Lookup Factory Methods</a>,
- * such as {@code Lookup#findVirtual Lookup.findVirtual},
+ * such as {@link Lookup#findVirtual Lookup.findVirtual},
* to resolve a symbolic reference into a method handle.
* A symbolic reference consists of a class, name string, and type.
- * <li>By calling the factory method {@code Lookup#unreflect Lookup.unreflect}
- * or {@code Lookup#unreflectSpecial Lookup.unreflectSpecial}
+ * <li>By calling the factory method {@link Lookup#unreflect Lookup.unreflect}
+ * or {@link Lookup#unreflectSpecial Lookup.unreflectSpecial}
* to convert a {@link Method} into a method handle.
- * <li>By calling the factory method {@code Lookup#unreflectConstructor Lookup.unreflectConstructor}
+ * <li>By calling the factory method {@link Lookup#unreflectConstructor Lookup.unreflectConstructor}
* to convert a {@link Constructor} into a method handle.
- * <li>By calling the factory method {@code Lookup#unreflectGetter Lookup.unreflectGetter}
- * or {@code Lookup#unreflectSetter Lookup.unreflectSetter}
+ * <li>By calling the factory method {@link Lookup#unreflectGetter Lookup.unreflectGetter}
+ * or {@link Lookup#unreflectSetter Lookup.unreflectSetter}
* to convert a {@link Field} into a method handle.
* </ul>
*
@@ -65,7 +66,7 @@
* <p>
* If the underlying method is <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a>,
* the direct method handle will have been "bound" to a particular caller class, the
- * {@code java.lang.invoke.MethodHandles.Lookup#lookupClass() lookup class}
+ * {@linkplain java.lang.invoke.MethodHandles.Lookup#lookupClass() lookup class}
* of the lookup object used to create it.
* Cracking this method handle with a different lookup class will fail
* even if the underlying method is public (like {@code Class.forName}).
@@ -73,7 +74,7 @@
* The requirement of lookup object matching provides a "fast fail" behavior
* for programs which may otherwise trust erroneous revelation of a method
* handle with symbolic information (or caller binding) from an unexpected scope.
- * Use {@code java.lang.invoke.MethodHandles#reflectAs} to override this limitation.
+ * Use {@link java.lang.invoke.MethodHandles#reflectAs} to override this limitation.
*
* <h1><a name="refkinds"></a>Reference kinds</h1>
* The <a href="MethodHandles.Lookup.html#lookups">Lookup Factory Methods</a>
@@ -120,7 +121,6 @@
* </table>
* @since 1.8
*/
-// TODO(narayan) : Change @code back to @link once MethodHandles has been imported.
public
interface MethodHandleInfo {
/**
@@ -198,14 +198,7 @@
* @exception NullPointerException if either argument is {@code null}
* @exception IllegalArgumentException if the underlying member is not accessible to the given lookup object
*/
- // TODO(narayan): change the second argument back to MethodHandles.Lookup once that
- // class has been introduced.
- //
- // public <T extends Member> T reflectAs(Class<T> expected, Lookup lookup);
- //
- // We need to temporarily keep the existing version because of a circular dependency between
- // the two classes.
- public <T extends Member> T reflectAs(Class<T> expected, Object lookup);
+ public <T extends Member> T reflectAs(Class<T> expected, Lookup lookup);
/**
* Returns the access modifiers of the underlying member.
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
index 9599f27..9c3b56d 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
@@ -868,9 +868,6 @@
* @throws NullPointerException if any argument is null
*/
public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
- // TODO: Support varargs methods. The returned method handle must be a var-args
- // collector in that case.
-
// Special case : when we're looking up a virtual method on the MethodHandles class
// itself, we can return one of our specialized invokers.
if (refc == MethodHandle.class) {
@@ -953,9 +950,6 @@
* @throws NullPointerException if any argument is null
*/
public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
- // TODO: Support varargs methods. The returned method handle must be a var-args
- // collector in that case.
-
// The queried |type| is (PT1,PT2,..)V
Constructor constructor = refc.getDeclaredConstructor(type.ptypes());
if (constructor == null) {
diff --git a/ojluni/src/main/java/java/lang/reflect/Proxy.java b/ojluni/src/main/java/java/lang/reflect/Proxy.java
index 209ccb1..c9e56cf 100644
--- a/ojluni/src/main/java/java/lang/reflect/Proxy.java
+++ b/ojluni/src/main/java/java/lang/reflect/Proxy.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1999, 2010, 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
@@ -26,25 +26,30 @@
package java.lang.reflect;
+
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
-import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiFunction;
+import libcore.util.EmptyArray;
import sun.reflect.CallerSensitive;
import sun.reflect.misc.ReflectUtil;
import sun.security.util.SecurityConstants;
-import libcore.util.EmptyArray;
/**
* {@code Proxy} provides static methods for creating dynamic proxy
@@ -54,16 +59,14 @@
* <p>To create a proxy for some interface {@code Foo}:
* <pre>
* InvocationHandler handler = new MyInvocationHandler(...);
- * Class proxyClass = Proxy.getProxyClass(
- * Foo.class.getClassLoader(), new Class[] { Foo.class });
- * Foo f = (Foo) proxyClass.
- * getConstructor(new Class[] { InvocationHandler.class }).
- * newInstance(new Object[] { handler });
+ * Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
+ * Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
+ * newInstance(handler);
* </pre>
* or more simply:
* <pre>
* Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
- * new Class[] { Foo.class },
+ * new Class<?>[] { Foo.class },
* handler);
* </pre>
*
@@ -92,7 +95,11 @@
* <p>A proxy class has the following properties:
*
* <ul>
- * <li>Proxy classes are public, final, and not abstract.
+ * <li>Proxy classes are <em>public, final, and not abstract</em> if
+ * all proxy interfaces are public.</li>
+ *
+ * <li>Proxy classes are <em>non-public, final, and not abstract</em> if
+ * any of the proxy interfaces is non-public.</li>
*
* <li>The unqualified name of a proxy class is unspecified. The space
* of class names that begin with the string {@code "$Proxy"}
@@ -236,23 +243,14 @@
private final static String proxyClassNamePrefix = "$Proxy";
/** parameter types of a proxy class constructor */
- private final static Class[] constructorParams =
+ private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
- /** maps a class loader to the proxy class cache for that loader */
- private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache
- = new WeakHashMap<>();
-
- /** marks that a particular proxy class is currently being generated */
- private static Object pendingGenerationMarker = new Object();
-
- /** next number to use for generation of unique proxy class names */
- private static long nextUniqueNumber = 0;
- private static Object nextUniqueNumberLock = new Object();
-
- /** set of all generated proxy classes, for isProxyClass implementation */
- private static Map<Class<?>, Void> proxyClasses =
- Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());
+ /**
+ * a cache of proxy classes
+ */
+ private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
+ proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
/**
* the invocation handler for this proxy instance.
@@ -296,9 +294,13 @@
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
- * @param h the invocation handler for this proxy instance
+ * @param h the invocation handler for this proxy instance
+ *
+ * @throws NullPointerException if the given invocation handler, {@code h},
+ * is {@code null}.
*/
protected Proxy(InvocationHandler h) {
+ Objects.requireNonNull(h);
this.h = h;
}
@@ -306,9 +308,10 @@
* Returns the {@code java.lang.Class} object for a proxy class
* given a class loader and an array of interfaces. The proxy class
* will be defined by the specified class loader and will implement
- * all of the supplied interfaces. If a proxy class for the same
- * permutation of interfaces has already been defined by the class
- * loader, then the existing proxy class will be returned; otherwise,
+ * all of the supplied interfaces. If any of the given interfaces
+ * is non-public, the proxy class will be non-public. If a proxy class
+ * for the same permutation of interfaces has already been defined by the
+ * class loader, then the existing proxy class will be returned; otherwise,
* a proxy class for those interfaces will be generated dynamically
* and defined by the class loader.
*
@@ -373,6 +376,21 @@
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
+ * @throws SecurityException if a security manager, <em>s</em>, is present
+ * and any of the following conditions is met:
+ * <ul>
+ * <li> the given {@code loader} is {@code null} and
+ * the caller's class loader is not {@code null} and the
+ * invocation of {@link SecurityManager#checkPermission
+ * s.checkPermission} with
+ * {@code RuntimePermission("getClassLoader")} permission
+ * denies access.</li>
+ * <li> for each proxy interface, {@code intf},
+ * the caller's class loader is not the same as or an
+ * ancestor of the class loader for {@code intf} and
+ * invocation of {@link SecurityManager#checkPackageAccess
+ * s.checkPackageAccess()} denies access to {@code intf}.</li>
+ * </ul>
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}
*/
@@ -394,142 +412,204 @@
throw new IllegalArgumentException("interface limit exceeded");
}
- Class<?> proxyClass = null;
+ // If the proxy class defined by the given loader implementing
+ // the given interfaces exists, this will simply return the cached copy;
+ // otherwise, it will create the proxy class via the ProxyClassFactory
+ return proxyClassCache.get(loader, interfaces);
+ }
- /* collect interface names to use as key for proxy class cache */
- String[] interfaceNames = new String[interfaces.length];
+ /*
+ * a key used for proxy class with 0 implemented interfaces
+ */
+ private static final Object key0 = new Object();
- // for detecting duplicates
- Set<Class<?>> interfaceSet = new HashSet<>();
+ /*
+ * Key1 and Key2 are optimized for the common use of dynamic proxies
+ * that implement 1 or 2 interfaces.
+ */
- for (int i = 0; i < interfaces.length; i++) {
- /*
- * Verify that the class loader resolves the name of this
- * interface to the same Class object.
- */
- String interfaceName = interfaces[i].getName();
- Class<?> interfaceClass = null;
- try {
- interfaceClass = Class.forName(interfaceName, false, loader);
- } catch (ClassNotFoundException e) {
- }
- if (interfaceClass != interfaces[i]) {
- throw new IllegalArgumentException(
- interfaces[i] + " is not visible from class loader");
- }
+ /*
+ * a key used for proxy class with 1 implemented interface
+ */
+ private static final class Key1 extends WeakReference<Class<?>> {
+ private final int hash;
- /*
- * Verify that the Class object actually represents an
- * interface.
- */
- if (!interfaceClass.isInterface()) {
- throw new IllegalArgumentException(
- interfaceClass.getName() + " is not an interface");
- }
-
- /*
- * Verify that this interface is not a duplicate.
- */
- if (interfaceSet.contains(interfaceClass)) {
- throw new IllegalArgumentException(
- "repeated interface: " + interfaceClass.getName());
- }
- interfaceSet.add(interfaceClass);
-
- interfaceNames[i] = interfaceName;
+ Key1(Class<?> intf) {
+ super(intf);
+ this.hash = intf.hashCode();
}
- /*
- * Using string representations of the proxy interfaces as
- * keys in the proxy class cache (instead of their Class
- * objects) is sufficient because we require the proxy
- * interfaces to be resolvable by name through the supplied
- * class loader, and it has the advantage that using a string
- * representation of a class makes for an implicit weak
- * reference to the class.
- */
- List<String> key = Arrays.asList(interfaceNames);
-
- /*
- * Find or create the proxy class cache for the class loader.
- */
- Map<List<String>, Object> cache;
- synchronized (loaderToCache) {
- cache = loaderToCache.get(loader);
- if (cache == null) {
- cache = new HashMap<>();
- loaderToCache.put(loader, cache);
- }
- /*
- * This mapping will remain valid for the duration of this
- * method, without further synchronization, because the mapping
- * will only be removed if the class loader becomes unreachable.
- */
+ @Override
+ public int hashCode() {
+ return hash;
}
- /*
- * Look up the list of interfaces in the proxy class cache using
- * the key. This lookup will result in one of three possible
- * kinds of values:
- * null, if there is currently no proxy class for the list of
- * interfaces in the class loader,
- * the pendingGenerationMarker object, if a proxy class for the
- * list of interfaces is currently being generated,
- * or a weak reference to a Class object, if a proxy class for
- * the list of interfaces has already been generated.
- */
- synchronized (cache) {
- /*
- * Note that we need not worry about reaping the cache for
- * entries with cleared weak references because if a proxy class
- * has been garbage collected, its class loader will have been
- * garbage collected as well, so the entire cache will be reaped
- * from the loaderToCache map.
- */
- do {
- Object value = cache.get(key);
- if (value instanceof Reference) {
- proxyClass = (Class<?>) ((Reference) value).get();
+ @Override
+ public boolean equals(Object obj) {
+ Class<?> intf;
+ return this == obj ||
+ obj != null &&
+ obj.getClass() == Key1.class &&
+ (intf = get()) != null &&
+ intf == ((Key1) obj).get();
+ }
+ }
+
+ /*
+ * a key used for proxy class with 2 implemented interfaces
+ */
+ private static final class Key2 extends WeakReference<Class<?>> {
+ private final int hash;
+ private final WeakReference<Class<?>> ref2;
+
+ Key2(Class<?> intf1, Class<?> intf2) {
+ super(intf1);
+ hash = 31 * intf1.hashCode() + intf2.hashCode();
+ ref2 = new WeakReference<Class<?>>(intf2);
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ Class<?> intf1, intf2;
+ return this == obj ||
+ obj != null &&
+ obj.getClass() == Key2.class &&
+ (intf1 = get()) != null &&
+ intf1 == ((Key2) obj).get() &&
+ (intf2 = ref2.get()) != null &&
+ intf2 == ((Key2) obj).ref2.get();
+ }
+ }
+
+ /*
+ * a key used for proxy class with any number of implemented interfaces
+ * (used here for 3 or more only)
+ */
+ private static final class KeyX {
+ private final int hash;
+ private final WeakReference<Class<?>>[] refs;
+
+ @SuppressWarnings("unchecked")
+ KeyX(Class<?>[] interfaces) {
+ hash = Arrays.hashCode(interfaces);
+ refs = (WeakReference<Class<?>>[])new WeakReference<?>[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++) {
+ refs[i] = new WeakReference<>(interfaces[i]);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj ||
+ obj != null &&
+ obj.getClass() == KeyX.class &&
+ equals(refs, ((KeyX) obj).refs);
+ }
+
+ private static boolean equals(WeakReference<Class<?>>[] refs1,
+ WeakReference<Class<?>>[] refs2) {
+ if (refs1.length != refs2.length) {
+ return false;
+ }
+ for (int i = 0; i < refs1.length; i++) {
+ Class<?> intf = refs1[i].get();
+ if (intf == null || intf != refs2[i].get()) {
+ return false;
}
- if (proxyClass != null) {
- // proxy class already generated: return it
- return proxyClass;
- } else if (value == pendingGenerationMarker) {
- // proxy class being generated: wait for it
- try {
- cache.wait();
- } catch (InterruptedException e) {
- /*
- * The class generation that we are waiting for should
- * take a small, bounded time, so we can safely ignore
- * thread interrupts here.
- */
- }
- continue;
- } else {
- /*
- * No proxy class for this list of interfaces has been
- * generated or is being generated, so we will go and
- * generate it now. Mark it as pending generation.
- */
- cache.put(key, pendingGenerationMarker);
- break;
- }
- } while (true);
+ }
+ return true;
}
+ }
- try {
+ /**
+ * A function that maps an array of interfaces to an optimal key where
+ * Class objects representing interfaces are weakly referenced.
+ */
+ private static final class KeyFactory
+ implements BiFunction<ClassLoader, Class<?>[], Object>
+ {
+ @Override
+ public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
+ switch (interfaces.length) {
+ case 1: return new Key1(interfaces[0]); // the most frequent
+ case 2: return new Key2(interfaces[0], interfaces[1]);
+ case 0: return key0;
+ default: return new KeyX(interfaces);
+ }
+ }
+ }
+
+ /**
+ * A factory function that generates, defines and returns the proxy class given
+ * the ClassLoader and array of interfaces.
+ */
+ private static final class ProxyClassFactory
+ implements BiFunction<ClassLoader, Class<?>[], Class<?>>
+ {
+ // prefix for all proxy class names
+ private static final String proxyClassNamePrefix = "$Proxy";
+
+ // next number to use for generation of unique proxy class names
+ private static final AtomicLong nextUniqueNumber = new AtomicLong();
+
+ @Override
+ public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
+
+ Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
+ for (Class<?> intf : interfaces) {
+ /*
+ * Verify that the class loader resolves the name of this
+ * interface to the same Class object.
+ */
+ Class<?> interfaceClass = null;
+ try {
+ interfaceClass = Class.forName(intf.getName(), false, loader);
+ } catch (ClassNotFoundException e) {
+ }
+ if (interfaceClass != intf) {
+ throw new IllegalArgumentException(
+ intf + " is not visible from class loader");
+ }
+ /*
+ * Verify that the Class object actually represents an
+ * interface.
+ */
+ if (!interfaceClass.isInterface()) {
+ throw new IllegalArgumentException(
+ interfaceClass.getName() + " is not an interface");
+ }
+ /*
+ * Verify that this interface is not a duplicate.
+ */
+ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
+ throw new IllegalArgumentException(
+ "repeated interface: " + interfaceClass.getName());
+ }
+ }
+
String proxyPkg = null; // package to define proxy class in
+ int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
- for (int i = 0; i < interfaces.length; i++) {
- int flags = interfaces[i].getModifiers();
+ for (Class<?> intf : interfaces) {
+ int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
- String name = interfaces[i].getName();
+ accessFlags = Modifier.FINAL;
+ String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
@@ -560,36 +640,13 @@
/*
* Choose a name for the proxy class to generate.
*/
- final long num;
- synchronized (nextUniqueNumberLock) {
- num = nextUniqueNumber++;
- }
+ long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
- proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
- exceptionsArray);
- }
- // add to set of all generated proxy classes, for isProxyClass
- proxyClasses.put(proxyClass, null);
-
- } finally {
- /*
- * We must clean up the "pending generation" state of the proxy
- * class cache entry somehow. If a proxy class was successfully
- * generated, store it in the cache (with a weak reference);
- * otherwise, remove the reserved entry. In all cases, notify
- * all waiters on reserved entries in this cache.
- */
- synchronized (cache) {
- if (proxyClass != null) {
- cache.put(key, new WeakReference<Class<?>>(proxyClass));
- } else {
- cache.remove(key);
- }
- cache.notifyAll();
+ return generateProxy(proxyName, interfaces, loader, methodsArray,
+ exceptionsArray);
}
}
- return proxyClass;
}
/**
@@ -643,7 +700,6 @@
return intersection.toArray(new Class<?>[intersection.size()]);
}
-
/**
* Throws if any two methods in {@code methods} have the same name and
* parameters but incompatible return types.
@@ -696,15 +752,11 @@
}
}
+
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
- * handler. This method is equivalent to:
- * <pre>
- * Proxy.getProxyClass(loader, interfaces).
- * getConstructor(new Class[] { InvocationHandler.class }).
- * newInstance(new Object[] { handler });
- * </pre>
+ * handler.
*
* <p>{@code Proxy.newProxyInstance} throws
* {@code IllegalArgumentException} for the same reasons that
@@ -720,6 +772,27 @@
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
+ * @throws SecurityException if a security manager, <em>s</em>, is present
+ * and any of the following conditions is met:
+ * <ul>
+ * <li> the given {@code loader} is {@code null} and
+ * the caller's class loader is not {@code null} and the
+ * invocation of {@link SecurityManager#checkPermission
+ * s.checkPermission} with
+ * {@code RuntimePermission("getClassLoader")} permission
+ * denies access;</li>
+ * <li> for each proxy interface, {@code intf},
+ * the caller's class loader is not the same as or an
+ * ancestor of the class loader for {@code intf} and
+ * invocation of {@link SecurityManager#checkPackageAccess
+ * s.checkPackageAccess()} denies access to {@code intf};</li>
+ * <li> any of the given proxy interfaces is non-public and the
+ * caller class is not in the same {@linkplain Package runtime package}
+ * as the non-public interface and the invocation of
+ * {@link SecurityManager#checkPermission s.checkPermission} with
+ * {@code ReflectPermission("newProxyInPackage.{package name}")}
+ * permission denies access.</li>
+ * </ul>
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}, or
* if the invocation handler, {@code h}, is
@@ -731,38 +804,47 @@
InvocationHandler h)
throws IllegalArgumentException
{
- if (h == null) {
- throw new NullPointerException();
- }
+ Objects.requireNonNull(h);
+
+ final Class<?>[] intfs = interfaces.clone();
+ // Android-changed: sm is always null
+ // final SecurityManager sm = System.getSecurityManager();
+ // if (sm != null) {
+ // checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
+ // }
/*
* Look up or generate the designated proxy class.
*/
- Class<?> cl = getProxyClass0(loader, interfaces);
+ Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
- final Constructor<?> cons = cl.getConstructor(constructorParams);
- return newInstance(cons, h);
- } catch (NoSuchMethodException e) {
- throw new InternalError(e.toString());
- }
- }
+ // Android-changed: sm is always null
+ // if (sm != null) {
+ // checkNewProxyPermission(Reflection.getCallerClass(), cl);
+ // }
- private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
- try {
- return cons.newInstance(new Object[] {h} );
- } catch (IllegalAccessException | InstantiationException e) {
- throw new InternalError(e.toString());
+ final Constructor<?> cons = cl.getConstructor(constructorParams);
+ final InvocationHandler ih = h;
+ if (!Modifier.isPublic(cl.getModifiers())) {
+ // Android-changed: Removed AccessController.doPrivileged
+ cons.setAccessible(true);
+ }
+ return cons.newInstance(new Object[]{h});
+ } catch (IllegalAccessException|InstantiationException e) {
+ throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
- throw new InternalError(t.toString());
+ throw new InternalError(t.toString(), t);
}
+ } catch (NoSuchMethodException e) {
+ throw new InternalError(e.toString(), e);
}
}
@@ -781,11 +863,7 @@
* @throws NullPointerException if {@code cl} is {@code null}
*/
public static boolean isProxyClass(Class<?> cl) {
- if (cl == null) {
- throw new NullPointerException();
- }
-
- return proxyClasses.containsKey(cl);
+ return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}
/**
@@ -795,19 +873,40 @@
* @return the invocation handler for the proxy instance
* @throws IllegalArgumentException if the argument is not a
* proxy instance
+ * @throws SecurityException if a security manager, <em>s</em>, is present
+ * and the caller's class loader is not the same as or an
+ * ancestor of the class loader for the invocation handler
+ * and invocation of {@link SecurityManager#checkPackageAccess
+ * s.checkPackageAccess()} denies access to the invocation
+ * handler's class.
*/
+ @CallerSensitive
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
{
/*
* Verify that the object is actually a proxy instance.
*/
- if (!(proxy instanceof Proxy)) {
+ if (!isProxyClass(proxy.getClass())) {
throw new IllegalArgumentException("not a proxy instance");
}
- return ((Proxy) proxy).h;
+
+ final Proxy p = (Proxy) proxy;
+ final InvocationHandler ih = p.h;
+ // Android changed, System.getSecurityManager() is always null
+ // if (System.getSecurityManager() != null) {
+ // Class<?> ihClass = ih.getClass();
+ // Class<?> caller = Reflection.getCallerClass();
+ // if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),
+ // ihClass.getClassLoader()))
+ // {
+ // ReflectUtil.checkPackageAccess(ihClass);
+ // }
+ // }
+ return ih;
}
+ // Android-changed: helper for art native code.
private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
InvocationHandler h = proxy.h;
return h.invoke(proxy, method, args);
diff --git a/ojluni/src/main/java/java/lang/reflect/WeakCache.java b/ojluni/src/main/java/java/lang/reflect/WeakCache.java
new file mode 100644
index 0000000..4c28024
--- /dev/null
+++ b/ojluni/src/main/java/java/lang/reflect/WeakCache.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 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
+ * 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 java.lang.reflect;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+/**
+ * Cache mapping pairs of {@code (key, sub-key) -> value}. Keys and values are
+ * weakly but sub-keys are strongly referenced. Keys are passed directly to
+ * {@link #get} method which also takes a {@code parameter}. Sub-keys are
+ * calculated from keys and parameters using the {@code subKeyFactory} function
+ * passed to the constructor. Values are calculated from keys and parameters
+ * using the {@code valueFactory} function passed to the constructor.
+ * Keys can be {@code null} and are compared by identity while sub-keys returned by
+ * {@code subKeyFactory} or values returned by {@code valueFactory}
+ * can not be null. Sub-keys are compared using their {@link #equals} method.
+ * Entries are expunged from cache lazily on each invocation to {@link #get},
+ * {@link #containsValue} or {@link #size} methods when the WeakReferences to
+ * keys are cleared. Cleared WeakReferences to individual values don't cause
+ * expunging, but such entries are logically treated as non-existent and
+ * trigger re-evaluation of {@code valueFactory} on request for their
+ * key/subKey.
+ *
+ * @author Peter Levart
+ * @param <K> type of keys
+ * @param <P> type of parameters
+ * @param <V> type of values
+ */
+final class WeakCache<K, P, V> {
+
+ private final ReferenceQueue<K> refQueue
+ = new ReferenceQueue<>();
+ // the key type is Object for supporting null key
+ private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
+ = new ConcurrentHashMap<>();
+ private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
+ = new ConcurrentHashMap<>();
+ private final BiFunction<K, P, ?> subKeyFactory;
+ private final BiFunction<K, P, V> valueFactory;
+
+ /**
+ * Construct an instance of {@code WeakCache}
+ *
+ * @param subKeyFactory a function mapping a pair of
+ * {@code (key, parameter) -> sub-key}
+ * @param valueFactory a function mapping a pair of
+ * {@code (key, parameter) -> value}
+ * @throws NullPointerException if {@code subKeyFactory} or
+ * {@code valueFactory} is null.
+ */
+ public WeakCache(BiFunction<K, P, ?> subKeyFactory,
+ BiFunction<K, P, V> valueFactory) {
+ this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
+ this.valueFactory = Objects.requireNonNull(valueFactory);
+ }
+
+ /**
+ * Look-up the value through the cache. This always evaluates the
+ * {@code subKeyFactory} function and optionally evaluates
+ * {@code valueFactory} function if there is no entry in the cache for given
+ * pair of (key, subKey) or the entry has already been cleared.
+ *
+ * @param key possibly null key
+ * @param parameter parameter used together with key to create sub-key and
+ * value (should not be null)
+ * @return the cached value (never null)
+ * @throws NullPointerException if {@code parameter} passed in or
+ * {@code sub-key} calculated by
+ * {@code subKeyFactory} or {@code value}
+ * calculated by {@code valueFactory} is null.
+ */
+ public V get(K key, P parameter) {
+ Objects.requireNonNull(parameter);
+
+ expungeStaleEntries();
+
+ Object cacheKey = CacheKey.valueOf(key, refQueue);
+
+ // lazily install the 2nd level valuesMap for the particular cacheKey
+ ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
+ if (valuesMap == null) {
+ ConcurrentMap<Object, Supplier<V>> oldValuesMap
+ = map.putIfAbsent(cacheKey,
+ valuesMap = new ConcurrentHashMap<>());
+ if (oldValuesMap != null) {
+ valuesMap = oldValuesMap;
+ }
+ }
+
+ // create subKey and retrieve the possible Supplier<V> stored by that
+ // subKey from valuesMap
+ Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
+ Supplier<V> supplier = valuesMap.get(subKey);
+ Factory factory = null;
+
+ while (true) {
+ if (supplier != null) {
+ // supplier might be a Factory or a CacheValue<V> instance
+ V value = supplier.get();
+ if (value != null) {
+ return value;
+ }
+ }
+ // else no supplier in cache
+ // or a supplier that returned null (could be a cleared CacheValue
+ // or a Factory that wasn't successful in installing the CacheValue)
+
+ // lazily construct a Factory
+ if (factory == null) {
+ factory = new Factory(key, parameter, subKey, valuesMap);
+ }
+
+ if (supplier == null) {
+ supplier = valuesMap.putIfAbsent(subKey, factory);
+ if (supplier == null) {
+ // successfully installed Factory
+ supplier = factory;
+ }
+ // else retry with winning supplier
+ } else {
+ if (valuesMap.replace(subKey, supplier, factory)) {
+ // successfully replaced
+ // cleared CacheEntry / unsuccessful Factory
+ // with our Factory
+ supplier = factory;
+ } else {
+ // retry with current supplier
+ supplier = valuesMap.get(subKey);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks whether the specified non-null value is already present in this
+ * {@code WeakCache}. The check is made using identity comparison regardless
+ * of whether value's class overrides {@link Object#equals} or not.
+ *
+ * @param value the non-null value to check
+ * @return true if given {@code value} is already cached
+ * @throws NullPointerException if value is null
+ */
+ public boolean containsValue(V value) {
+ Objects.requireNonNull(value);
+
+ expungeStaleEntries();
+ return reverseMap.containsKey(new LookupValue<>(value));
+ }
+
+ /**
+ * Returns the current number of cached entries that
+ * can decrease over time when keys/values are GC-ed.
+ */
+ public int size() {
+ expungeStaleEntries();
+ return reverseMap.size();
+ }
+
+ private void expungeStaleEntries() {
+ CacheKey<K> cacheKey;
+ while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
+ cacheKey.expungeFrom(map, reverseMap);
+ }
+ }
+
+ /**
+ * A factory {@link Supplier} that implements the lazy synchronized
+ * construction of the value and installment of it into the cache.
+ */
+ private final class Factory implements Supplier<V> {
+
+ private final K key;
+ private final P parameter;
+ private final Object subKey;
+ private final ConcurrentMap<Object, Supplier<V>> valuesMap;
+
+ Factory(K key, P parameter, Object subKey,
+ ConcurrentMap<Object, Supplier<V>> valuesMap) {
+ this.key = key;
+ this.parameter = parameter;
+ this.subKey = subKey;
+ this.valuesMap = valuesMap;
+ }
+
+ @Override
+ public synchronized V get() { // serialize access
+ // re-check
+ Supplier<V> supplier = valuesMap.get(subKey);
+ if (supplier != this) {
+ // something changed while we were waiting:
+ // might be that we were replaced by a CacheValue
+ // or were removed because of failure ->
+ // return null to signal WeakCache.get() to retry
+ // the loop
+ return null;
+ }
+ // else still us (supplier == this)
+
+ // create new value
+ V value = null;
+ try {
+ value = Objects.requireNonNull(valueFactory.apply(key, parameter));
+ } finally {
+ if (value == null) { // remove us on failure
+ valuesMap.remove(subKey, this);
+ }
+ }
+ // the only path to reach here is with non-null value
+ assert value != null;
+
+ // wrap value with CacheValue (WeakReference)
+ CacheValue<V> cacheValue = new CacheValue<>(value);
+
+ // try replacing us with CacheValue (this should always succeed)
+ if (valuesMap.replace(subKey, this, cacheValue)) {
+ // put also in reverseMap
+ reverseMap.put(cacheValue, Boolean.TRUE);
+ } else {
+ throw new AssertionError("Should not reach here");
+ }
+
+ // successfully replaced us with new CacheValue -> return the value
+ // wrapped by it
+ return value;
+ }
+ }
+
+ /**
+ * Common type of value suppliers that are holding a referent.
+ * The {@link #equals} and {@link #hashCode} of implementations is defined
+ * to compare the referent by identity.
+ */
+ private interface Value<V> extends Supplier<V> {}
+
+ /**
+ * An optimized {@link Value} used to look-up the value in
+ * {@link WeakCache#containsValue} method so that we are not
+ * constructing the whole {@link CacheValue} just to look-up the referent.
+ */
+ private static final class LookupValue<V> implements Value<V> {
+ private final V value;
+
+ LookupValue(V value) {
+ this.value = value;
+ }
+
+ @Override
+ public V get() {
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(value); // compare by identity
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == this ||
+ obj instanceof Value &&
+ this.value == ((Value<?>) obj).get(); // compare by identity
+ }
+ }
+
+ /**
+ * A {@link Value} that weakly references the referent.
+ */
+ private static final class CacheValue<V>
+ extends WeakReference<V> implements Value<V>
+ {
+ private final int hash;
+
+ CacheValue(V value) {
+ super(value);
+ this.hash = System.identityHashCode(value); // compare by identity
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ V value;
+ return obj == this ||
+ obj instanceof Value &&
+ // cleared CacheValue is only equal to itself
+ (value = get()) != null &&
+ value == ((Value<?>) obj).get(); // compare by identity
+ }
+ }
+
+ /**
+ * CacheKey containing a weakly referenced {@code key}. It registers
+ * itself with the {@code refQueue} so that it can be used to expunge
+ * the entry when the {@link WeakReference} is cleared.
+ */
+ private static final class CacheKey<K> extends WeakReference<K> {
+
+ // a replacement for null keys
+ private static final Object NULL_KEY = new Object();
+
+ static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
+ return key == null
+ // null key means we can't weakly reference it,
+ // so we use a NULL_KEY singleton as cache key
+ ? NULL_KEY
+ // non-null key requires wrapping with a WeakReference
+ : new CacheKey<>(key, refQueue);
+ }
+
+ private final int hash;
+
+ private CacheKey(K key, ReferenceQueue<K> refQueue) {
+ super(key, refQueue);
+ this.hash = System.identityHashCode(key); // compare by identity
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ K key;
+ return obj == this ||
+ obj != null &&
+ obj.getClass() == this.getClass() &&
+ // cleared CacheKey is only equal to itself
+ (key = this.get()) != null &&
+ // compare key by identity
+ key == ((CacheKey<K>) obj).get();
+ }
+
+ void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
+ ConcurrentMap<?, Boolean> reverseMap) {
+ // removing just by key is always safe here because after a CacheKey
+ // is cleared and enqueue-ed it is only equal to itself
+ // (see equals method)...
+ ConcurrentMap<?, ?> valuesMap = map.remove(this);
+ // remove also from reverseMap if needed
+ if (valuesMap != null) {
+ for (Object cacheValue : valuesMap.values()) {
+ reverseMap.remove(cacheValue);
+ }
+ }
+ }
+ }
+}
diff --git a/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java b/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
index f6b221c..ea45500 100644
--- a/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
+++ b/ojluni/src/main/java/java/net/PlainDatagramSocketImpl.java
@@ -24,23 +24,11 @@
*/
package java.net;
-import android.system.StructGroupReq;
-
import java.io.IOException;
-import libcore.io.IoBridge;
-import libcore.util.EmptyArray;
-
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
import jdk.net.*;
-
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.AF_UNSPEC;
-import static android.system.OsConstants.MSG_PEEK;
-import static android.system.OsConstants.POLLIN;
-import static android.system.OsConstants.SOCK_DGRAM;
-import static libcore.io.IoBridge.JAVA_IP_MULTICAST_TTL;
-import static libcore.io.IoBridge.JAVA_MCAST_JOIN_GROUP;
-import static libcore.io.IoBridge.JAVA_MCAST_LEAVE_GROUP;
-import static libcore.io.IoBridge.setSocketOption;
import static sun.net.ExtendedOptionsImpl.*;
/*
@@ -90,139 +78,44 @@
}
}
- protected synchronized void bind0(int lport, InetAddress laddr) throws SocketException {
- if (isClosed()) {
- throw new SocketException("Socket closed");
- }
+ protected synchronized native void bind0(int lport, InetAddress laddr)
+ throws SocketException;
- IoBridge.bind(fd, laddr, lport);
+ protected native void send(DatagramPacket p) throws IOException;
- if (lport == 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 = lport;
- }
- }
+ protected synchronized native int peek(InetAddress i) throws IOException;
- protected void send(DatagramPacket p) throws IOException {
- if (isClosed()) {
- throw new SocketException("Socket closed");
- }
- if (p.getData() == null || p.getAddress() == null) {
- throw new NullPointerException("null buffer || null address");
- }
+ protected synchronized native int peekData(DatagramPacket p) throws IOException;
- int port = connected ? 0 : p.getPort();
- InetAddress address = connected ? null : p.getAddress();
- IoBridge.sendto(fd, p.getData(), p.getOffset(), p.getLength(), 0, address, port);
- }
+ protected synchronized native void receive0(DatagramPacket p)
+ throws IOException;
- protected synchronized int peek(InetAddress i) throws IOException {
- DatagramPacket p = new DatagramPacket(EmptyArray.BYTE, 0);
- doRecv(p, MSG_PEEK);
- i.holder().address = p.getAddress().holder().address;
- return p.getPort();
- }
+ protected native void setTimeToLive(int ttl) throws IOException;
- protected synchronized int peekData(DatagramPacket p) throws IOException {
- doRecv(p, MSG_PEEK);
- return p.getPort();
- }
+ protected native int getTimeToLive() throws IOException;
- protected synchronized void receive0(DatagramPacket p) throws IOException {
- doRecv(p, 0);
- }
+ protected native void setTTL(byte ttl) throws IOException;
- private void doRecv(DatagramPacket p, int flags) throws IOException {
- if (isClosed()) {
- throw new SocketException("Socket closed");
- }
+ protected native byte getTTL() throws IOException;
- if (timeout != 0) {
- IoBridge.poll(fd, POLLIN, timeout);
- }
+ protected native void join(InetAddress inetaddr, NetworkInterface netIf)
+ throws IOException;
- IoBridge.recvfrom(false, fd, p.getData(), p.getOffset(), p.getLength(), flags, p,
- connected);
- }
+ protected native void leave(InetAddress inetaddr, NetworkInterface netIf)
+ throws IOException;
- protected void setTimeToLive(int ttl) throws IOException {
- IoBridge.setSocketOption(fd, JAVA_IP_MULTICAST_TTL, ttl);
- }
+ protected native void datagramSocketCreate() throws SocketException;
- protected int getTimeToLive() throws IOException {
- return (Integer) IoBridge.getSocketOption(fd, JAVA_IP_MULTICAST_TTL);
- }
-
- protected void setTTL(byte ttl) throws IOException {
- setTimeToLive((int) ttl & 0xff);
- }
-
- protected byte getTTL() throws IOException {
- return (byte) getTimeToLive();
- }
-
- private static StructGroupReq makeGroupReq(InetAddress gr_group,
- NetworkInterface networkInterface) {
- int gr_interface = (networkInterface != null) ? networkInterface.getIndex() : 0;
- return new StructGroupReq(gr_interface, gr_group);
- }
-
- protected void join(InetAddress inetaddr, NetworkInterface netIf) throws IOException {
- if (isClosed()) {
- throw new SocketException("Socket closed");
- }
-
- IoBridge.setSocketOption(fd, JAVA_MCAST_JOIN_GROUP, makeGroupReq(inetaddr, netIf));
- }
-
- protected void leave(InetAddress inetaddr, NetworkInterface netIf)
- throws IOException {
- if (isClosed()) {
- throw new SocketException("Socket closed");
- }
-
- IoBridge.setSocketOption(fd, JAVA_MCAST_LEAVE_GROUP, makeGroupReq(inetaddr, netIf));
- }
-
- protected void datagramSocketCreate() throws SocketException {
- fd = IoBridge.socket(AF_INET6, SOCK_DGRAM, 0);
- setSocketOption(fd, SO_BROADCAST, true);
- }
-
- protected void datagramSocketClose() {
- try {
- IoBridge.closeAndSignalBlockedThreads(fd);
- } catch (IOException ignored) { }
- }
+ protected native void datagramSocketClose();
protected native void socketSetOption0(int opt, Object val)
throws SocketException;
protected native Object socketGetOption(int opt) throws SocketException;
- protected void connect0(InetAddress address, int port) throws SocketException {
- if (isClosed()) {
- throw new SocketException("Socket closed");
- }
+ protected native void connect0(InetAddress address, int port) throws SocketException;
- IoBridge.connect(fd, address, port);
- }
-
- protected void disconnect0(int family) {
- if (isClosed()) {
- return;
- }
-
- InetAddress inetAddressUnspec = new InetAddress();
- inetAddressUnspec.holder().family = AF_UNSPEC;
-
- try {
- IoBridge.connect(fd, inetAddressUnspec, 0);
- } catch (SocketException ignored) { }
- }
+ protected native void disconnect0(int family);
/**
* Perform class load-time initializations.
diff --git a/ojluni/src/main/java/java/nio/ByteBuffer.java b/ojluni/src/main/java/java/nio/ByteBuffer.java
index c78447f..7f51a61 100644
--- a/ojluni/src/main/java/java/nio/ByteBuffer.java
+++ b/ojluni/src/main/java/java/nio/ByteBuffer.java
@@ -555,14 +555,14 @@
// isDirect() doesn't imply !hasArray(), ByteBuffer.allocateDirect allocated buffer will
// have a backing, non-gc-movable byte array. JNI allocated direct byte buffers WILL NOT
// have a backing array.
- final Object srcObject = src.isDirect() ? src : src.array();
+ final Object srcObject = src.isDirect() ? src : src.hb;
int srcOffset = src.position();
if (!src.isDirect()) {
srcOffset += src.offset;
}
final ByteBuffer dst = this;
- final Object dstObject = dst.isDirect() ? dst : dst.array();
+ final Object dstObject = dst.isDirect() ? dst : dst.hb;
int dstOffset = dst.position();
if (!dst.isDirect()) {
dstOffset += dst.offset;
diff --git a/ojluni/src/main/java/java/util/ArrayDeque.java b/ojluni/src/main/java/java/util/ArrayDeque.java
index 83ae076..ecaf0e8 100644
--- a/ojluni/src/main/java/java/util/ArrayDeque.java
+++ b/ojluni/src/main/java/java/util/ArrayDeque.java
@@ -27,6 +27,7 @@
* License version 2 only, as published by the Free Software Foundation.
* However, the following notice accompanied the original version of this
* file:
+ *
* Written by Josh Bloch of Google Inc. and released to the public domain,
* as explained at http://creativecommons.org/publicdomain/zero/1.0/.
*/
@@ -907,7 +908,7 @@
* @since 1.8
*/
public Spliterator<E> spliterator() {
- return new DeqSpliterator<E>(this, -1, -1);
+ return new DeqSpliterator<>(this, -1, -1);
}
static final class DeqSpliterator<E> implements Spliterator<E> {
diff --git a/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java b/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java
index d3b5614..87c04d1 100644
--- a/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java
+++ b/ojluni/src/main/java/java/util/ArrayPrefixHelpers.java
@@ -1,4 +1,33 @@
/*
+ * 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.
+ */
+
+/*
+ * This file is available under and governed by the GNU General Public
+ * License version 2 only, as published by the Free Software Foundation.
+ * However, the following notice accompanied the original version of this
+ * file:
+ *
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/Deque.java b/ojluni/src/main/java/java/util/Deque.java
index 9fec73b..3520f53 100644
--- a/ojluni/src/main/java/java/util/Deque.java
+++ b/ojluni/src/main/java/java/util/Deque.java
@@ -27,6 +27,7 @@
* License version 2 only, as published by the Free Software Foundation.
* However, the following notice accompanied the original version of this
* file:
+ *
* Written by Doug Lea and Josh Bloch with assistance from members of
* JCP JSR-166 Expert Group and released to the public domain, as explained
* at http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/NavigableMap.java b/ojluni/src/main/java/java/util/NavigableMap.java
index fdbc0b5..66b57c7 100644
--- a/ojluni/src/main/java/java/util/NavigableMap.java
+++ b/ojluni/src/main/java/java/util/NavigableMap.java
@@ -27,6 +27,7 @@
* License version 2 only, as published by the Free Software Foundation.
* However, the following notice accompanied the original version of this
* file:
+ *
* Written by Doug Lea and Josh Bloch with assistance from members of JCP
* JSR-166 Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/NavigableSet.java b/ojluni/src/main/java/java/util/NavigableSet.java
index 412a07d..71d8dba 100644
--- a/ojluni/src/main/java/java/util/NavigableSet.java
+++ b/ojluni/src/main/java/java/util/NavigableSet.java
@@ -27,6 +27,7 @@
* License version 2 only, as published by the Free Software Foundation.
* However, the following notice accompanied the original version of this
* file:
+ *
* Written by Doug Lea and Josh Bloch with assistance from members of JCP
* JSR-166 Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/Queue.java b/ojluni/src/main/java/java/util/Queue.java
index 6520337..cbdc205 100644
--- a/ojluni/src/main/java/java/util/Queue.java
+++ b/ojluni/src/main/java/java/util/Queue.java
@@ -27,6 +27,7 @@
* License version 2 only, as published by the Free Software Foundation.
* However, the following notice accompanied the original version of this
* file:
+ *
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/ojluni/src/main/java/java/util/SplittableRandom.java b/ojluni/src/main/java/java/util/SplittableRandom.java
index f17c37a..579102a 100644
--- a/ojluni/src/main/java/java/util/SplittableRandom.java
+++ b/ojluni/src/main/java/java/util/SplittableRandom.java
@@ -34,7 +34,6 @@
import java.util.stream.LongStream;
import java.util.stream.StreamSupport;
-
/**
* A generator of uniform pseudorandom values applicable for use in
* (among other contexts) isolated parallel computations that may
diff --git a/ojluni/src/main/java/java/util/TreeMap.java b/ojluni/src/main/java/java/util/TreeMap.java
index b1e7e21..6b18547 100644
--- a/ojluni/src/main/java/java/util/TreeMap.java
+++ b/ojluni/src/main/java/java/util/TreeMap.java
@@ -536,7 +536,31 @@
public V put(K key, V value) {
TreeMapEntry<K,V> t = root;
if (t == null) {
- compare(key, key); // type (and possibly null) check
+ // We could just call compare(key, key) for its side effect of checking the type and
+ // nullness of the input key. However, several applications seem to have written comparators
+ // that only expect to be called on elements that aren't equal to each other (after
+ // making assumptions about the domain of the map). Clearly, such comparators are bogus
+ // because get() would never work, but TreeSets are frequently used for sorting a set
+ // of distinct elements.
+ //
+ // As a temporary work around, we perform the null & instanceof checks by hand so that
+ // we can guarantee that elements are never compared against themselves.
+ //
+ // compare(key, key);
+ //
+ // **** THIS CHANGE WILL BE REVERTED IN A FUTURE ANDROID RELEASE ****
+ if (comparator != null) {
+ if (key == null) {
+ comparator.compare(key, key);
+ }
+ } else {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ } else if (!(key instanceof Comparable)) {
+ throw new ClassCastException(
+ "Cannot cast" + key.getClass().getName() + " to Comparable.");
+ }
+ }
root = new TreeMapEntry<>(key, value, null);
size = 1;
diff --git a/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java b/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java
index 2a5f5de..d3d681f 100644
--- a/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java
+++ b/ojluni/src/main/java/java/util/concurrent/CopyOnWriteArrayList.java
@@ -100,21 +100,22 @@
final transient Object lock = new Object();
/** The array, accessed only via getArray/setArray. */
- private transient volatile Object[] array;
+ // Android-changed: renamed array -> elements for backwards compatibility b/33916927
+ private transient volatile Object[] elements;
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
- return array;
+ return elements;
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
- array = a;
+ elements = a;
}
/**
diff --git a/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java b/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
index cda4cbb..e2e25f7 100644
--- a/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
+++ b/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 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
@@ -305,8 +305,10 @@
* @param key key whose mapping is to be removed from the preference node.
* @throws IllegalStateException if this node (or an ancestor) has been
* removed with the {@link #removeNode()} method.
+ * @throws NullPointerException {@inheritDoc}.
*/
public void remove(String key) {
+ Objects.requireNonNull(key, "Specified key cannot be null");
synchronized(lock) {
if (removed)
throw new IllegalStateException("Node has been removed.");
@@ -1086,6 +1088,8 @@
* removed. (The implementor needn't check for any of these things.)
*
* <p>This method is invoked with the lock on this node held.
+ * @param key the key
+ * @param value the value
*/
protected abstract void putSpi(String key, String value);
@@ -1104,6 +1108,7 @@
*
* <p>This method is invoked with the lock on this node held.
*
+ * @param key the key
* @return the value associated with the specified key at this preference
* node, or <tt>null</tt> if there is no association for this
* key, or the association cannot be determined at this time.
@@ -1117,6 +1122,7 @@
* (The implementor needn't check for either of these things.)
*
* <p>This method is invoked with the lock on this node held.
+ * @param key the key
*/
protected abstract void removeSpi(String key);
diff --git a/ojluni/src/main/java/java/util/prefs/PreferenceChangeListener.java b/ojluni/src/main/java/java/util/prefs/PreferenceChangeListener.java
index 2c0c51e..0fb96c5 100644
--- a/ojluni/src/main/java/java/util/prefs/PreferenceChangeListener.java
+++ b/ojluni/src/main/java/java/util/prefs/PreferenceChangeListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 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
@@ -34,6 +34,7 @@
* @see NodeChangeListener
* @since 1.4
*/
+@FunctionalInterface
public interface PreferenceChangeListener extends java.util.EventListener {
/**
* This method gets called when a preference is added, removed or when
diff --git a/ojluni/src/main/java/java/util/prefs/Preferences.java b/ojluni/src/main/java/java/util/prefs/Preferences.java
index 285ba7d..aaf9950 100644
--- a/ojluni/src/main/java/java/util/prefs/Preferences.java
+++ b/ojluni/src/main/java/java/util/prefs/Preferences.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 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
@@ -135,52 +135,52 @@
* subsequently restore from the backup.
*
* <p>The XML document has the following DOCTYPE declaration:
- * <pre>
- * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
- * </pre>
+ * <pre>{@code
+ * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+ * }</pre>
* Note that the system URI (http://java.sun.com/dtd/preferences.dtd) is
* <i>not</i> accessed when exporting or importing preferences; it merely
* serves as a string to uniquely identify the DTD, which is:
- * <pre>
- * <?xml version="1.0" encoding="UTF-8"?>
+ * <pre>{@code
+ * <?xml version="1.0" encoding="UTF-8"?>
*
- * <!-- DTD for a Preferences tree. -->
+ * <!-- DTD for a Preferences tree. -->
*
- * <!-- The preferences element is at the root of an XML document
- * representing a Preferences tree. -->
- * <!ELEMENT preferences (root)>
+ * <!-- The preferences element is at the root of an XML document
+ * representing a Preferences tree. -->
+ * <!ELEMENT preferences (root)>
*
- * <!-- The preferences element contains an optional version attribute,
- * which specifies version of DTD. -->
- * <!ATTLIST preferences EXTERNAL_XML_VERSION CDATA "0.0" >
+ * <!-- The preferences element contains an optional version attribute,
+ * which specifies version of DTD. -->
+ * <!ATTLIST preferences EXTERNAL_XML_VERSION CDATA "0.0" >
*
- * <!-- The root element has a map representing the root's preferences
- * (if any), and one node for each child of the root (if any). -->
- * <!ELEMENT root (map, node*) >
+ * <!-- The root element has a map representing the root's preferences
+ * (if any), and one node for each child of the root (if any). -->
+ * <!ELEMENT root (map, node*) >
*
- * <!-- Additionally, the root contains a type attribute, which
- * specifies whether it's the system or user root. -->
- * <!ATTLIST root
- * type (system|user) #REQUIRED >
+ * <!-- Additionally, the root contains a type attribute, which
+ * specifies whether it's the system or user root. -->
+ * <!ATTLIST root
+ * type (system|user) #REQUIRED >
*
- * <!-- Each node has a map representing its preferences (if any),
- * and one node for each child (if any). -->
- * <!ELEMENT node (map, node*) >
+ * <!-- Each node has a map representing its preferences (if any),
+ * and one node for each child (if any). -->
+ * <!ELEMENT node (map, node*) >
*
- * <!-- Additionally, each node has a name attribute -->
- * <!ATTLIST node
- * name CDATA #REQUIRED >
+ * <!-- Additionally, each node has a name attribute -->
+ * <!ATTLIST node
+ * name CDATA #REQUIRED >
*
- * <!-- A map represents the preferences stored at a node (if any). -->
- * <!ELEMENT map (entry*) >
+ * <!-- A map represents the preferences stored at a node (if any). -->
+ * <!ELEMENT map (entry*) >
*
- * <!-- An entry represents a single preference, which is simply
- * a key-value pair. -->
- * <!ELEMENT entry EMPTY >
- * <!ATTLIST entry
+ * <!-- An entry represents a single preference, which is simply
+ * a key-value pair. -->
+ * <!ELEMENT entry EMPTY >
+ * <!ATTLIST entry
* key CDATA #REQUIRED
- * value CDATA #REQUIRED >
- * </pre>
+ * value CDATA #REQUIRED >
+ * }</pre>
*
* Every <tt>Preferences</tt> implementation must have an associated {@link
* PreferencesFactory} implementation. Every Java(TM) SE implementation must provide
@@ -372,7 +372,7 @@
* @throws IllegalArgumentException if the package has node preferences
* node associated with it.
*/
- private static String nodeName(Class c) {
+ private static String nodeName(Class<?> c) {
if (c.isArray())
throw new IllegalArgumentException(
"Arrays have no associated preferences node.");
@@ -1124,9 +1124,9 @@
* This XML document is, in effect, an offline backup of the node.
*
* <p>The XML document will have the following DOCTYPE declaration:
- * <pre>
- * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
- * </pre>
+ * <pre>{@code
+ * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+ * }</pre>
* The UTF-8 character encoding will be used.
*
* <p>This method is an exception to the general rule that the results of
@@ -1155,9 +1155,9 @@
* effect, an offline backup of the subtree rooted at the node.
*
* <p>The XML document will have the following DOCTYPE declaration:
- * <pre>
- * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
- * </pre>
+ * <pre>{@code
+ * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+ * }</pre>
* The UTF-8 character encoding will be used.
*
* <p>This method is an exception to the general rule that the results of
@@ -1191,9 +1191,9 @@
* do not exist, the nodes will be created.
*
* <p>The XML document must have the following DOCTYPE declaration:
- * <pre>
- * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
- * </pre>
+ * <pre>{@code
+ * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+ * }</pre>
* (This method is designed for use in conjunction with
* {@link #exportNode(OutputStream)} and
* {@link #exportSubtree(OutputStream)}.
diff --git a/ojluni/src/main/java/java/util/prefs/PreferencesFactory.java b/ojluni/src/main/java/java/util/prefs/PreferencesFactory.java
index 8bf37bc..d7341e6 100644
--- a/ojluni/src/main/java/java/util/prefs/PreferencesFactory.java
+++ b/ojluni/src/main/java/java/util/prefs/PreferencesFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 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
@@ -45,6 +45,7 @@
/**
* Returns the system root preference node. (Multiple calls on this
* method will return the same object reference.)
+ * @return the system root preference node
*/
Preferences systemRoot();
@@ -52,6 +53,8 @@
* Returns the user root preference node corresponding to the calling
* user. In a server, the returned value will typically depend on
* some implicit client-context.
+ * @return the user root preference node corresponding to the calling
+ * user
*/
Preferences userRoot();
}
diff --git a/ojluni/src/main/java/java/util/prefs/XmlSupport.java b/ojluni/src/main/java/java/util/prefs/XmlSupport.java
index 8356e1d..b1703d5 100644
--- a/ojluni/src/main/java/java/util/prefs/XmlSupport.java
+++ b/ojluni/src/main/java/java/util/prefs/XmlSupport.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2002, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2012, 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
@@ -93,7 +93,7 @@
* @throws BackingStoreException if preference data cannot be read from
* backing store.
* @throws IllegalStateException if this node (or an ancestor) has been
- * removed with the {@link #removeNode()} method.
+ * removed with the {@link Preferences#removeNode()} method.
*/
static void export(OutputStream os, final Preferences p, boolean subTree)
throws IOException, BackingStoreException {
@@ -107,7 +107,7 @@
xmlRoot.setAttribute("type", (p.isUserNode() ? "user" : "system"));
// Get bottom-up list of nodes from p to root, excluding root
- List ancestors = new ArrayList();
+ List<Preferences> ancestors = new ArrayList<>();
for (Preferences kid = p, dad = kid.parent(); dad != null;
kid = dad, dad = kid.parent()) {
@@ -117,7 +117,7 @@
for (int i=ancestors.size()-1; i >= 0; i--) {
e.appendChild(doc.createElement("map"));
e = (Element) e.appendChild(doc.createElement("node"));
- e.setAttribute("name", ((Preferences)ancestors.get(i)).name());
+ e.setAttribute("name", ancestors.get(i).name());
}
putPreferencesInXml(e, doc, p, subTree);
@@ -365,17 +365,17 @@
* @throws IOException if writing to the specified output stream
* results in an <tt>IOException</tt>.
*/
- static void exportMap(OutputStream os, Map map) throws IOException {
+ static void exportMap(OutputStream os, Map<String, String> map) throws IOException {
Document doc = createPrefsDoc("map");
Element xmlMap = doc.getDocumentElement( ) ;
xmlMap.setAttribute("MAP_XML_VERSION", MAP_XML_VERSION);
- for (Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
- Map.Entry e = (Map.Entry) i.next();
+ for (Iterator<Map.Entry<String, String>> i = map.entrySet().iterator(); i.hasNext(); ) {
+ Map.Entry<String, String> e = i.next();
Element xe = (Element)
xmlMap.appendChild(doc.createElement("entry"));
- xe.setAttribute("key", (String) e.getKey());
- xe.setAttribute("value", (String) e.getValue());
+ xe.setAttribute("key", e.getKey());
+ xe.setAttribute("value", e.getValue());
}
writeDoc(doc, os);
@@ -394,7 +394,7 @@
* @throws InvalidPreferencesFormatException Data on input stream does not
* constitute a valid XML document with the mandated document type.
*/
- static void importMap(InputStream is, Map m)
+ static void importMap(InputStream is, Map<String, String> m)
throws IOException, InvalidPreferencesFormatException
{
try {
diff --git a/ojluni/src/main/native/PlainDatagramSocketImpl.c b/ojluni/src/main/native/PlainDatagramSocketImpl.c
index 9acf306..b36e286 100644
--- a/ojluni/src/main/native/PlainDatagramSocketImpl.c
+++ b/ojluni/src/main/native/PlainDatagramSocketImpl.c
@@ -171,6 +171,874 @@
}
/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: bind
+ * Signature: (ILjava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_bind0(JNIEnv *env, jobject this,
+ jint localport, jobject iaObj) {
+ /* fdObj is the FileDescriptor field on this */
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ /* fd is an int field on fdObj */
+ int fd;
+ int len = 0;
+ 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;
+ }
+
+ /* 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, pdsi_localPortID, localport);
+ } else {
+ (*env)->SetIntField(env, this, pdsi_localPortID, localport);
+ }
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: connect0
+ * Signature: (Ljava/net/InetAddress;I)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_connect0(JNIEnv *env, jobject this,
+ jobject address, jint port) {
+ /* The object's field */
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ /* The fdObj'fd */
+ jint fd;
+ /* The packetAddress address, family and port */
+ SOCKADDR rmtaddr;
+ int len = 0;
+
+ if (IS_NULL(fdObj)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Socket closed");
+ return;
+ }
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+ if (IS_NULL(address)) {
+ JNU_ThrowNullPointerException(env, "address");
+ return;
+ }
+
+ if (NET_InetAddressToSockaddr(env, address, port, (struct sockaddr *)&rmtaddr, &len, JNI_TRUE) != 0) {
+ return;
+ }
+
+ setDefaultScopeID(env, (struct sockaddr *)&rmtaddr);
+ if (JVM_Connect(fd, (struct sockaddr *)&rmtaddr, len) == -1) {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException",
+ "Connect failed");
+ return;
+ }
+
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: disconnect0
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_disconnect0(JNIEnv *env, jobject this, jint family) {
+ /* The object's field */
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ /* The fdObj'fd */
+ jint fd;
+
+#if defined(__linux__) || defined(_ALLBSD_SOURCE)
+ SOCKADDR addr;
+ int len;
+#endif
+
+ if (IS_NULL(fdObj)) {
+ return;
+ }
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+#if defined(__linux__) || defined(_ALLBSD_SOURCE)
+ memset(&addr, 0, sizeof(addr));
+#ifdef AF_INET6
+ if (ipv6_available()) {
+ struct sockaddr_in6 *him6 = (struct sockaddr_in6 *)&addr;
+ him6->sin6_family = AF_UNSPEC;
+ len = sizeof(struct sockaddr_in6);
+ } else
+#endif
+ {
+ struct sockaddr_in *him4 = (struct sockaddr_in*)&addr;
+ him4->sin_family = AF_UNSPEC;
+ len = sizeof(struct sockaddr_in);
+ }
+ JVM_Connect(fd, (struct sockaddr *)&addr, len);
+
+#ifdef __linux__
+ int localPort = 0;
+ if (JVM_GetSockName(fd, (struct sockaddr *)&addr, &len) == -1)
+ return;
+ localPort = NET_GetPortFromSockaddr((struct sockaddr *)&addr);
+ if (localPort == 0) {
+ localPort = (*env)->GetIntField(env, this, pdsi_localPortID);
+#ifdef AF_INET6
+ if (((struct sockaddr*)&addr)->sa_family == AF_INET6) {
+ ((struct sockaddr_in6*)&addr)->sin6_port = htons(localPort);
+ } else
+#endif /* AF_INET6 */
+ {
+ ((struct sockaddr_in*)&addr)->sin_port = htons(localPort);
+ }
+ NET_Bind(fd, (struct sockaddr *)&addr, len);
+ }
+#endif
+#else
+ JVM_Connect(fd, 0, 0);
+#endif
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: send
+ * Signature: (Ljava/net/DatagramPacket;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_send(JNIEnv *env, jobject this,
+ jobject packet) {
+
+ char BUF[MAX_BUFFER_LEN];
+ char *fullPacket = NULL;
+ int ret, mallocedPacket = JNI_FALSE;
+ /* The object's field */
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ jint trafficClass = (*env)->GetIntField(env, this, pdsi_trafficClassID);
+
+ jbyteArray packetBuffer;
+ jobject packetAddress;
+ jint packetBufferOffset, packetBufferLen, packetPort;
+ jboolean connected;
+
+ /* The fdObj'fd */
+ jint fd;
+
+ SOCKADDR rmtaddr, *rmtaddrP=&rmtaddr;
+ int len;
+
+ if (IS_NULL(fdObj)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Socket closed");
+ return;
+ }
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+ if (IS_NULL(packet)) {
+ JNU_ThrowNullPointerException(env, "packet");
+ return;
+ }
+
+ connected = (*env)->GetBooleanField(env, this, pdsi_connected);
+
+ packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+ packetAddress = (*env)->GetObjectField(env, packet, dp_addressID);
+ if (IS_NULL(packetBuffer) || IS_NULL(packetAddress)) {
+ JNU_ThrowNullPointerException(env, "null buffer || null address");
+ return;
+ }
+
+ packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+ packetBufferLen = (*env)->GetIntField(env, packet, dp_lengthID);
+
+ if (connected) {
+ /* arg to NET_Sendto () null in this case */
+ len = 0;
+ rmtaddrP = 0;
+ } else {
+ packetPort = (*env)->GetIntField(env, packet, dp_portID);
+ if (NET_InetAddressToSockaddr(env, packetAddress, packetPort, (struct sockaddr *)&rmtaddr, &len, JNI_TRUE) != 0) {
+ return;
+ }
+ }
+ setDefaultScopeID(env, (struct sockaddr *)&rmtaddr);
+
+ if (packetBufferLen > MAX_BUFFER_LEN) {
+ /* When JNI-ifying the JDK's IO routines, we turned
+ * reads and writes of byte arrays of size greater
+ * than 2048 bytes into several operations of size 2048.
+ * This saves a malloc()/memcpy()/free() for big
+ * buffers. This is OK for file IO and TCP, but that
+ * strategy violates the semantics of a datagram protocol.
+ * (one big send) != (several smaller sends). So here
+ * we *must* allocate the buffer. Note it needn't be bigger
+ * than 65,536 (0xFFFF), the max size of an IP packet.
+ * Anything bigger should be truncated anyway.
+ *
+ * We may want to use a smarter allocation scheme at some
+ * point.
+ */
+ if (packetBufferLen > MAX_PACKET_LEN) {
+ packetBufferLen = MAX_PACKET_LEN;
+ }
+ fullPacket = (char *)malloc(packetBufferLen);
+
+ if (!fullPacket) {
+ JNU_ThrowOutOfMemoryError(env, "Send buffer native heap allocation failed");
+ return;
+ } else {
+ mallocedPacket = JNI_TRUE;
+ }
+ } else {
+ fullPacket = &(BUF[0]);
+ }
+
+ (*env)->GetByteArrayRegion(env, packetBuffer, packetBufferOffset, packetBufferLen,
+ (jbyte *)fullPacket);
+#ifdef AF_INET6
+ if (trafficClass != 0 && ipv6_available()) {
+ NET_SetTrafficClass((struct sockaddr *)&rmtaddr, trafficClass);
+ }
+#endif /* AF_INET6 */
+
+
+ /*
+ * Send the datagram.
+ *
+ * If we are connected it's possible that sendto will return
+ * ECONNREFUSED indicating that an ICMP port unreachable has
+ * received.
+ */
+ ret = NET_SendTo(fd, fullPacket, packetBufferLen, 0,
+ (struct sockaddr *)rmtaddrP, len);
+
+ if (ret < 0) {
+ switch (ret) {
+ case JVM_IO_ERR :
+ if (errno == ECONNREFUSED) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+ "ICMP Port Unreachable");
+ } else {
+ NET_ThrowByNameWithLastError(env, "java/io/IOException", "sendto failed");
+ }
+ break;
+
+ case JVM_IO_INTR:
+ JNU_ThrowByName(env, "java/io/InterruptedIOException",
+ "operation interrupted");
+ break;
+ }
+ }
+
+ if (mallocedPacket) {
+ free(fullPacket);
+ }
+ return;
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: peek
+ * Signature: (Ljava/net/InetAddress;)I
+ */
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_peek(JNIEnv *env, jobject this,
+ jobject addressObj) {
+
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+ jint fd;
+ ssize_t n;
+ SOCKADDR remote_addr;
+ int len;
+ char buf[1];
+ jint family;
+ jobject iaObj;
+ int port;
+ if (IS_NULL(fdObj)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+ return -1;
+ } else {
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+ }
+ if (IS_NULL(addressObj)) {
+ JNU_ThrowNullPointerException(env, "Null address in peek()");
+ return -1;
+ }
+ if (timeout) {
+ int ret = NET_Timeout(fd, timeout);
+ if (ret == 0) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+ "Peek timed out");
+ return ret;
+ } else if (ret == JVM_IO_ERR) {
+ if (errno == EBADF) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+ } else {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Peek failed");
+ }
+ return ret;
+ } else if (ret == JVM_IO_INTR) {
+ JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+ "operation interrupted");
+ return ret; /* WARNING: SHOULD WE REALLY RETURN -2??? */
+ }
+ }
+
+ len = SOCKADDR_LEN;
+ n = NET_RecvFrom(fd, buf, 1, MSG_PEEK,
+ (struct sockaddr *)&remote_addr, &len);
+
+ if (n == JVM_IO_ERR) {
+
+#ifdef __solaris__
+ if (errno == ECONNREFUSED) {
+ int orig_errno = errno;
+ (void) recv(fd, buf, 1, 0);
+ errno = orig_errno;
+ }
+#endif
+ if (errno == ECONNREFUSED) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+ "ICMP Port Unreachable");
+ } else {
+ if (errno == EBADF) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+ } else {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Peek failed");
+ }
+ }
+ return 0;
+ } else if (n == JVM_IO_INTR) {
+ JNU_ThrowByName(env, "java/io/InterruptedIOException", 0);
+ return 0;
+ }
+
+ iaObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+#ifdef AF_INET6
+ family = getInetAddress_family(env, iaObj) == IPv4? AF_INET : AF_INET6;
+#else
+ family = AF_INET;
+#endif
+ if (family == AF_INET) { /* this API can't handle IPV6 addresses */
+ int address = getInetAddress_addr(env, iaObj);
+ setInetAddress_addr(env, addressObj, address);
+ }
+ return port;
+}
+
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_peekData(JNIEnv *env, jobject this,
+ jobject packet) {
+
+ char BUF[MAX_BUFFER_LEN];
+ char *fullPacket = NULL;
+ int mallocedPacket = JNI_FALSE;
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+
+ jbyteArray packetBuffer;
+ jint packetBufferOffset, packetBufferLen;
+
+ int fd;
+
+ int n;
+ SOCKADDR remote_addr;
+ int len;
+ int port;
+
+ if (IS_NULL(fdObj)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Socket closed");
+ return -1;
+ }
+
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+ if (IS_NULL(packet)) {
+ JNU_ThrowNullPointerException(env, "packet");
+ return -1;
+ }
+
+ packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+ if (IS_NULL(packetBuffer)) {
+ JNU_ThrowNullPointerException(env, "packet buffer");
+ return -1;
+ }
+ packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+ packetBufferLen = (*env)->GetIntField(env, packet, dp_bufLengthID);
+ if (timeout) {
+ int ret = NET_Timeout(fd, timeout);
+ if (ret == 0) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+ "Receive timed out");
+ return -1;
+ } else if (ret == JVM_IO_ERR) {
+#ifdef __linux__
+ if (errno == EBADF) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+ } else {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+ }
+#else
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+#endif
+ return -1;
+ } else if (ret == JVM_IO_INTR) {
+ JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+ "operation interrupted");
+ return -1;
+ }
+ }
+
+ if (packetBufferLen > MAX_BUFFER_LEN) {
+
+ /* When JNI-ifying the JDK's IO routines, we turned
+ * reads and writes of byte arrays of size greater
+ * than 2048 bytes into several operations of size 2048.
+ * This saves a malloc()/memcpy()/free() for big
+ * buffers. This is OK for file IO and TCP, but that
+ * strategy violates the semantics of a datagram protocol.
+ * (one big send) != (several smaller sends). So here
+ * we *must* allocate the buffer. Note it needn't be bigger
+ * than 65,536 (0xFFFF), the max size of an IP packet.
+ * anything bigger is truncated anyway.
+ *
+ * We may want to use a smarter allocation scheme at some
+ * point.
+ */
+ if (packetBufferLen > MAX_PACKET_LEN) {
+ packetBufferLen = MAX_PACKET_LEN;
+ }
+ fullPacket = (char *)malloc(packetBufferLen);
+
+ if (!fullPacket) {
+ JNU_ThrowOutOfMemoryError(env, "Peek buffer native heap allocation failed");
+ return -1;
+ } else {
+ mallocedPacket = JNI_TRUE;
+ }
+ } else {
+ fullPacket = &(BUF[0]);
+ }
+
+ len = SOCKADDR_LEN;
+ n = NET_RecvFrom(fd, fullPacket, packetBufferLen, MSG_PEEK,
+ (struct sockaddr *)&remote_addr, &len);
+ /* truncate the data if the packet's length is too small */
+ if (n > packetBufferLen) {
+ n = packetBufferLen;
+ }
+ if (n == JVM_IO_ERR) {
+
+#ifdef __solaris__
+ if (errno == ECONNREFUSED) {
+ int orig_errno = errno;
+ (void) recv(fd, fullPacket, 1, 0);
+ errno = orig_errno;
+ }
+#endif
+ (*env)->SetIntField(env, packet, dp_offsetID, 0);
+ (*env)->SetIntField(env, packet, dp_lengthID, 0);
+ if (errno == ECONNREFUSED) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+ "ICMP Port Unreachable");
+ } else {
+ if (errno == EBADF) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+ } else {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+ }
+ }
+ } else if (n == JVM_IO_INTR) {
+ (*env)->SetIntField(env, packet, dp_offsetID, 0);
+ (*env)->SetIntField(env, packet, dp_lengthID, 0);
+ JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+ "operation interrupted");
+ } else {
+ /*
+ * success - fill in received address...
+ *
+ * REMIND: Fill in an int on the packet, and create inetadd
+ * object in Java, as a performance improvement. Also
+ * construct the inetadd object lazily.
+ */
+
+ jobject packetAddress;
+
+ /*
+ * Check if there is an InetAddress already associated with this
+ * packet. If so we check if it is the same source address. We
+ * can't update any existing InetAddress because it is immutable
+ */
+ packetAddress = (*env)->GetObjectField(env, packet, dp_addressID);
+ if (packetAddress != NULL) {
+ if (!NET_SockaddrEqualsInetAddress(env, (struct sockaddr *)&remote_addr, packetAddress)) {
+ /* force a new InetAddress to be created */
+ packetAddress = NULL;
+ }
+ }
+ if (packetAddress == NULL) {
+ packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+ /* stuff the new Inetaddress in the packet */
+ (*env)->SetObjectField(env, packet, dp_addressID, packetAddress);
+ } else {
+ /* only get the new port number */
+ port = NET_GetPortFromSockaddr((struct sockaddr *)&remote_addr);
+ }
+ /* and fill in the data, remote address/port and such */
+ (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n,
+ (jbyte *)fullPacket);
+ (*env)->SetIntField(env, packet, dp_portID, port);
+ (*env)->SetIntField(env, packet, dp_lengthID, n);
+ }
+
+ if (mallocedPacket) {
+ free(fullPacket);
+ }
+ return port;
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: receive
+ * Signature: (Ljava/net/DatagramPacket;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_receive0(JNIEnv *env, jobject this,
+ jobject packet) {
+
+ char BUF[MAX_BUFFER_LEN];
+ char *fullPacket = NULL;
+ int mallocedPacket = JNI_FALSE;
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ jint timeout = (*env)->GetIntField(env, this, pdsi_timeoutID);
+
+ jbyteArray packetBuffer;
+ jint packetBufferOffset, packetBufferLen;
+
+ int fd;
+
+ int n;
+ SOCKADDR remote_addr;
+ int len;
+ jboolean retry;
+#ifdef __linux__
+ jboolean connected = JNI_FALSE;
+ jobject connectedAddress = NULL;
+ jint connectedPort = 0;
+ jlong prevTime = 0;
+#endif
+
+ if (IS_NULL(fdObj)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Socket closed");
+ return;
+ }
+
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+
+ if (IS_NULL(packet)) {
+ JNU_ThrowNullPointerException(env, "packet");
+ return;
+ }
+
+ packetBuffer = (*env)->GetObjectField(env, packet, dp_bufID);
+ if (IS_NULL(packetBuffer)) {
+ JNU_ThrowNullPointerException(env, "packet buffer");
+ return;
+ }
+ packetBufferOffset = (*env)->GetIntField(env, packet, dp_offsetID);
+ packetBufferLen = (*env)->GetIntField(env, packet, dp_bufLengthID);
+
+ if (packetBufferLen > MAX_BUFFER_LEN) {
+
+ /* When JNI-ifying the JDK's IO routines, we turned
+ * reads and writes of byte arrays of size greater
+ * than 2048 bytes into several operations of size 2048.
+ * This saves a malloc()/memcpy()/free() for big
+ * buffers. This is OK for file IO and TCP, but that
+ * strategy violates the semantics of a datagram protocol.
+ * (one big send) != (several smaller sends). So here
+ * we *must* allocate the buffer. Note it needn't be bigger
+ * than 65,536 (0xFFFF), the max size of an IP packet.
+ * anything bigger is truncated anyway.
+ *
+ * We may want to use a smarter allocation scheme at some
+ * point.
+ */
+ if (packetBufferLen > MAX_PACKET_LEN) {
+ packetBufferLen = MAX_PACKET_LEN;
+ }
+ fullPacket = (char *)malloc(packetBufferLen);
+
+ if (!fullPacket) {
+ JNU_ThrowOutOfMemoryError(env, "Receive buffer native heap allocation failed");
+ return;
+ } else {
+ mallocedPacket = JNI_TRUE;
+ }
+ } else {
+ fullPacket = &(BUF[0]);
+ }
+
+
+ do {
+ retry = JNI_FALSE;
+
+ if (timeout) {
+ int ret = NET_Timeout(fd, timeout);
+ if (ret <= 0) {
+ if (ret == 0) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException",
+ "Receive timed out");
+ } else if (ret == JVM_IO_ERR) {
+#ifdef __linux__
+ if (errno == EBADF) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+ } else {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+ }
+#else
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+#endif
+ } else if (ret == JVM_IO_INTR) {
+ JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+ "operation interrupted");
+ }
+
+ if (mallocedPacket) {
+ free(fullPacket);
+ }
+
+ return;
+ }
+ }
+
+ len = SOCKADDR_LEN;
+ n = NET_RecvFrom(fd, fullPacket, packetBufferLen, 0,
+ (struct sockaddr *)&remote_addr, &len);
+ /* truncate the data if the packet's length is too small */
+ if (n > packetBufferLen) {
+ n = packetBufferLen;
+ }
+ if (n == JVM_IO_ERR) {
+ (*env)->SetIntField(env, packet, dp_offsetID, 0);
+ (*env)->SetIntField(env, packet, dp_lengthID, 0);
+ if (errno == ECONNREFUSED) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+ "ICMP Port Unreachable");
+ } else {
+ if (errno == EBADF) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
+ } else {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Receive failed");
+ }
+ }
+ } else if (n == JVM_IO_INTR) {
+ (*env)->SetIntField(env, packet, dp_offsetID, 0);
+ (*env)->SetIntField(env, packet, dp_lengthID, 0);
+ JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException",
+ "operation interrupted");
+ } else {
+ int port;
+ jobject packetAddress;
+
+ /*
+ * success - fill in received address...
+ *
+ * REMIND: Fill in an int on the packet, and create inetadd
+ * object in Java, as a performance improvement. Also
+ * construct the inetadd object lazily.
+ */
+
+ /*
+ * Check if there is an InetAddress already associated with this
+ * packet. If so we check if it is the same source address. We
+ * can't update any existing InetAddress because it is immutable
+ */
+ packetAddress = (*env)->GetObjectField(env, packet, dp_addressID);
+ if (packetAddress != NULL) {
+ if (!NET_SockaddrEqualsInetAddress(env, (struct sockaddr *)&remote_addr, packetAddress)) {
+ /* force a new InetAddress to be created */
+ packetAddress = NULL;
+ }
+ }
+ if (packetAddress == NULL) {
+ packetAddress = NET_SockaddrToInetAddress(env, (struct sockaddr *)&remote_addr, &port);
+ /* stuff the new Inetaddress in the packet */
+ (*env)->SetObjectField(env, packet, dp_addressID, packetAddress);
+ } else {
+ /* only get the new port number */
+ port = NET_GetPortFromSockaddr((struct sockaddr *)&remote_addr);
+ }
+ /* and fill in the data, remote address/port and such */
+ (*env)->SetByteArrayRegion(env, packetBuffer, packetBufferOffset, n,
+ (jbyte *)fullPacket);
+ (*env)->SetIntField(env, packet, dp_portID, port);
+ (*env)->SetIntField(env, packet, dp_lengthID, n);
+ }
+
+ } while (retry);
+
+ if (mallocedPacket) {
+ free(fullPacket);
+ }
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: datagramSocketCreate
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_datagramSocketCreate(JNIEnv *env,
+ jobject this) {
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ int arg, fd, t = 1;
+#ifdef AF_INET6
+ int domain = ipv6_available() ? AF_INET6 : AF_INET;
+#else
+ int domain = AF_INET;
+#endif
+
+ if (IS_NULL(fdObj)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Socket closed");
+ return;
+ }
+
+ if ((fd = JVM_Socket(domain, SOCK_DGRAM, 0)) == JVM_IO_ERR) {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+ "Error creating socket");
+ return;
+ }
+ tagSocket(env, fd);
+
+#ifdef AF_INET6
+ /* Disable IPV6_V6ONLY to ensure dual-socket support */
+ if (domain == AF_INET6) {
+ 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 */
+
+#ifdef __APPLE__
+ arg = 65507;
+ if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_SNDBUF,
+ (char *)&arg, sizeof(arg)) < 0) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ strerror(errno));
+ return;
+ }
+ if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_RCVBUF,
+ (char *)&arg, sizeof(arg)) < 0) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ strerror(errno));
+ return;
+ }
+#endif /* __APPLE__ */
+
+ setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char*) &t, sizeof(int));
+
+#if defined(__linux__)
+ arg = 0;
+ int level = (domain == AF_INET6) ? IPPROTO_IPV6 : IPPROTO_IP;
+ if ((setsockopt(fd, level, IP_MULTICAST_ALL, (char*)&arg, sizeof(arg)) < 0) &&
+ (errno != ENOPROTOOPT)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ strerror(errno));
+ close(fd);
+ return;
+ }
+#endif
+
+#if defined (__linux__) && defined (AF_INET6)
+ /*
+ * On Linux for IPv6 sockets we must set the hop limit
+ * to 1 to be compatible with default TTL of 1 for IPv4 sockets.
+ */
+ if (domain == AF_INET6) {
+ int ttl = 1;
+ setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl,
+ sizeof(ttl));
+ }
+#endif /* __linux__ */
+
+ (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd);
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: datagramSocketClose
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_datagramSocketClose(JNIEnv *env,
+ jobject this) {
+ /*
+ * REMIND: PUT A LOCK AROUND THIS CODE
+ */
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ int fd;
+
+ if (IS_NULL(fdObj)) {
+ return;
+ }
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+ if (fd == -1) {
+ return;
+ }
+ (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1);
+ untagSocket(env, fd);
+ NET_SocketClose(fd);
+}
+
+
+/*
* Set outgoing multicast interface designated by a NetworkInterface index.
* Throw exception if failed.
*
@@ -677,9 +1545,418 @@
return NULL;
}
+/*
+ * Multicast-related calls
+ */
+
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_setTTL(JNIEnv *env, jobject this,
+ jbyte ttl) {
+ jint ittl = ttl;
+ if (ittl < 0) {
+ ittl += 0x100;
+ }
+ PlainDatagramSocketImpl_setTimeToLive(env, this, ittl);
+}
+
+/*
+ * Set TTL for a socket. Throw exception if failed.
+ */
+static void setTTL(JNIEnv *env, int fd, jint ttl) {
+ char ittl = (char)ttl;
+ if (JVM_SetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&ittl,
+ sizeof(ittl)) < 0) {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+ "Error setting socket option");
+ }
+}
+
+/*
+ * Set hops limit for a socket. Throw exception if failed.
+ */
+#ifdef AF_INET6
+static void setHopLimit(JNIEnv *env, int fd, jint ttl) {
+ int ittl = (int)ttl;
+ if (JVM_SetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ (char*)&ittl, sizeof(ittl)) < 0) {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+ "Error setting socket option");
+ }
+}
+#endif
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: setTTL
+ * Signature: (B)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_setTimeToLive(JNIEnv *env, jobject this,
+ jint ttl) {
+
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ int fd;
+ /* it is important to cast this to a char, otherwise setsockopt gets confused */
+
+ if (IS_NULL(fdObj)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Socket closed");
+ return;
+ } else {
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+ }
+ /* setsockopt to be correct TTL */
+#ifdef AF_INET6
+#ifdef __linux__
+ setTTL(env, fd, ttl);
+ if ((*env)->ExceptionCheck(env)) {
+ return;
+ }
+ if (ipv6_available()) {
+ setHopLimit(env, fd, ttl);
+ }
+#else /* __linux__ not defined */
+ if (ipv6_available()) {
+ setHopLimit(env, fd, ttl);
+ } else {
+ setTTL(env, fd, ttl);
+ }
+#endif /* __linux__ */
+#else
+ setTTL(env, fd, ttl);
+#endif /* AF_INET6 */
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: getTTL
+ * Signature: ()B
+ */
+JNIEXPORT jbyte JNICALL
+PlainDatagramSocketImpl_getTTL(JNIEnv *env, jobject this) {
+ return (jbyte)PlainDatagramSocketImpl_getTimeToLive(env, this);
+}
+
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: getTTL
+ * Signature: ()B
+ */
+JNIEXPORT jint JNICALL
+PlainDatagramSocketImpl_getTimeToLive(JNIEnv *env, jobject this) {
+
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ jint fd = -1;
+
+ if (IS_NULL(fdObj)) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Socket closed");
+ return -1;
+ } else {
+ fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
+ }
+ /* getsockopt of TTL */
+#ifdef AF_INET6
+ if (ipv6_available()) {
+ int ttl = 0;
+ int len = sizeof(ttl);
+
+ if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ (char*)&ttl, &len) < 0) {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+ "Error getting socket option");
+ return -1;
+ }
+ return (jint)ttl;
+ } else
+#endif /* AF_INET6 */
+ {
+ u_char ttl = 0;
+ int len = sizeof(ttl);
+ if (JVM_GetSockOpt(fd, IPPROTO_IP, IP_MULTICAST_TTL,
+ (char*)&ttl, &len) < 0) {
+ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
+ "Error getting socket option");
+ return -1;
+ }
+ return (jint)ttl;
+ }
+}
+
+
+/*
+ * mcast_join_leave: Join or leave a multicast group.
+ *
+ * For IPv4 sockets use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP socket option
+ * to join/leave multicast group.
+ *
+ * For IPv6 sockets use IPV6_ADD_MEMBERSHIP/IPV6_DROP_MEMBERSHIP socket option
+ * to join/leave multicast group. If multicast group is an IPv4 address then
+ * an IPv4-mapped address is used.
+ *
+ * On Linux with IPv6 if we wish to join/leave an IPv4 multicast group then
+ * we must use the IPv4 socket options. This is because the IPv6 socket options
+ * don't support IPv4-mapped addresses. This is true as per 2.2.19 and 2.4.7
+ * kernel releases. In the future it's possible that IP_ADD_MEMBERSHIP
+ * will be updated to return ENOPROTOOPT if uses with an IPv6 socket (Solaris
+ * already does this). Thus to cater for this we first try with the IPv4
+ * socket options and if they fail we use the IPv6 socket options. This
+ * seems a reasonable failsafe solution.
+ */
+static void mcast_join_leave(JNIEnv *env, jobject this,
+ jobject iaObj, jobject niObj,
+ jboolean join) {
+
+ jobject fdObj = (*env)->GetObjectField(env, this, pdsi_fdID);
+ jint fd;
+ jint ipv6_join_leave;
+
+ 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");
+ return;
+ }
+
+ /*
+ * Get java/net/NetworkInterface#index field.
+ */
+ static jfieldID ni_indexID;
+
+ if (ni_indexID == NULL) {
+ jclass c = (*env)->FindClass(env, "java/net/NetworkInterface");
+ CHECK_NULL(c);
+ ni_indexID = (*env)->GetFieldID(env, c, "index", "I");
+ CHECK_NULL(ni_indexID);
+ }
+
+ /*
+ * Determine if this is an IPv4 or IPv6 join/leave.
+ */
+ ipv6_join_leave = ipv6_available();
+
+ if (getInetAddress_family(env, iaObj) == IPv4) {
+ ipv6_join_leave = JNI_FALSE;
+ }
+
+ /*
+ * For IPv4 join use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP socket option
+ *
+ * On Linux if IPv4 or IPv6 use IP_ADD_MEMBERSHIP/IP_DROP_MEMBERSHIP
+ */
+ if (!ipv6_join_leave) {
+ struct ip_mreqn mname;
+ int mname_len;
+
+ /*
+ * joinGroup(InetAddress, NetworkInterface) implementation :-
+ *
+ * Linux/IPv6: use ip_mreqn structure populated with multicast
+ * address and interface index.
+ *
+ */
+ if (niObj != NULL) {
+ mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj));
+ mname.imr_address.s_addr = 0;
+ mname.imr_ifindex = (*env)->GetIntField(env, niObj, ni_indexID);
+ mname_len = sizeof(struct ip_mreqn);
+ }
+
+
+ /*
+ * joinGroup(InetAddress) implementation :-
+ *
+ * Linux/IPv6: use ip_mreqn structure populated with multicast
+ * address and interface index. index obtained
+ * from cached value or IPV6_MULTICAST_IF.
+ *
+ * IPv4: use ip_mreq structure populated with multicast
+ * address and local address obtained from
+ * IP_MULTICAST_IF. On Linux IP_MULTICAST_IF
+ * returns different structure depending on
+ * kernel.
+ */
+
+ if (niObj == NULL) {
+ int index;
+ int len = sizeof(index);
+
+ if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+ (char*)&index, &len) < 0) {
+ NET_ThrowCurrent(env, "getsockopt IPV6_MULTICAST_IF failed");
+ return;
+ }
+
+ mname.imr_multiaddr.s_addr = htonl(getInetAddress_addr(env, iaObj));
+ mname.imr_address.s_addr = 0 ;
+ mname.imr_ifindex = index;
+ mname_len = sizeof(struct ip_mreqn);
+ }
+
+
+ /*
+ * Join the multicast group.
+ */
+ if (JVM_SetSockOpt(fd, IPPROTO_IP, (join ? IP_ADD_MEMBERSHIP:IP_DROP_MEMBERSHIP),
+ (char *) &mname, mname_len) < 0) {
+
+ /*
+ * If IP_ADD_MEMBERSHIP returns ENOPROTOOPT on Linux and we've got
+ * IPv6 enabled then it's possible that the kernel has been fixed
+ * so we switch to IPV6_ADD_MEMBERSHIP socket option.
+ * As of 2.4.7 kernel IPV6_ADD_MEMBERSHIP can't handle IPv4-mapped
+ * addresses so we have to use IP_ADD_MEMBERSHIP for IPv4 multicast
+ * groups. However if the socket is an IPv6 socket then then setsockopt
+ * should return ENOPROTOOPT. We assume this will be fixed in Linux
+ * at some stage.
+ */
+ if (errno == ENOPROTOOPT) {
+ if (ipv6_available()) {
+ ipv6_join_leave = JNI_TRUE;
+ errno = 0;
+ } else {
+ errno = ENOPROTOOPT; /* errno can be changed by ipv6_available */
+ }
+ }
+ if (errno) {
+ if (join) {
+ NET_ThrowCurrent(env, "setsockopt IP_ADD_MEMBERSHIP failed");
+ } else {
+ if (errno == ENOENT)
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Not a member of the multicast group");
+ else
+ NET_ThrowCurrent(env, "setsockopt IP_DROP_MEMBERSHIP failed");
+ }
+ return;
+ }
+ }
+
+ /*
+ * If we haven't switched to IPv6 socket option then we're done.
+ */
+ if (!ipv6_join_leave) {
+ return;
+ }
+ }
+
+
+ /*
+ * IPv6 join. If it's an IPv4 multicast group then we use an IPv4-mapped
+ * address.
+ */
+ {
+ struct ipv6_mreq mname6;
+
+ jbyteArray ipaddress;
+ jbyte caddr[16];
+ jint family;
+ jint address;
+ family = getInetAddress_family(env, iaObj) == IPv4? AF_INET : AF_INET6;
+ if (family == AF_INET) { /* will convert to IPv4-mapped address */
+ memset((char *) caddr, 0, 16);
+ address = getInetAddress_addr(env, iaObj);
+
+ caddr[10] = 0xff;
+ caddr[11] = 0xff;
+
+ caddr[12] = ((address >> 24) & 0xff);
+ caddr[13] = ((address >> 16) & 0xff);
+ caddr[14] = ((address >> 8) & 0xff);
+ caddr[15] = (address & 0xff);
+ } else {
+ // Android-changed: explicit cast to suppress compiler warning.
+ getInet6Address_ipaddress(env, iaObj, (char*)caddr);
+ }
+
+ memcpy((void *)&(mname6.ipv6mr_multiaddr), caddr, sizeof(struct in6_addr));
+ if (IS_NULL(niObj)) {
+ int index;
+ int len = sizeof(index);
+
+ if (JVM_GetSockOpt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
+ (char*)&index, &len) < 0) {
+ NET_ThrowCurrent(env, "getsockopt IPV6_MULTICAST_IF failed");
+ return;
+ }
+
+ mname6.ipv6mr_interface = index;
+ } else {
+ jint idx = (*env)->GetIntField(env, niObj, ni_indexID);
+ mname6.ipv6mr_interface = idx;
+ }
+
+#define ADD_MEMBERSHIP IPV6_ADD_MEMBERSHIP
+#define DRP_MEMBERSHIP IPV6_DROP_MEMBERSHIP
+#define S_ADD_MEMBERSHIP "IPV6_ADD_MEMBERSHIP"
+#define S_DRP_MEMBERSHIP "IPV6_DROP_MEMBERSHIP"
+
+ /* Join the multicast group */
+ if (JVM_SetSockOpt(fd, IPPROTO_IPV6, (join ? ADD_MEMBERSHIP : DRP_MEMBERSHIP),
+ (char *) &mname6, sizeof (mname6)) < 0) {
+
+ if (join) {
+ NET_ThrowCurrent(env, "setsockopt " S_ADD_MEMBERSHIP " failed");
+ } else {
+ if (errno == ENOENT) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
+ "Not a member of the multicast group");
+ } else {
+ NET_ThrowCurrent(env, "setsockopt " S_DRP_MEMBERSHIP " failed");
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: join
+ * Signature: (Ljava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_join(JNIEnv *env, jobject this,
+ jobject iaObj, jobject niObj)
+{
+ mcast_join_leave(env, this, iaObj, niObj, JNI_TRUE);
+}
+
+/*
+ * Class: java_net_PlainDatagramSocketImpl
+ * Method: leave
+ * Signature: (Ljava/net/InetAddress;)V
+ */
+JNIEXPORT void JNICALL
+PlainDatagramSocketImpl_leave(JNIEnv *env, jobject this,
+ jobject iaObj, jobject niObj)
+{
+ mcast_join_leave(env, this, iaObj, niObj, JNI_FALSE);
+}
+
static JNINativeMethod gMethods[] = {
+ NATIVE_METHOD(PlainDatagramSocketImpl, leave, "(Ljava/net/InetAddress;Ljava/net/NetworkInterface;)V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, join, "(Ljava/net/InetAddress;Ljava/net/NetworkInterface;)V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, getTimeToLive, "()I"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, getTTL, "()B"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, setTimeToLive, "(I)V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, setTTL, "(B)V"),
NATIVE_METHOD(PlainDatagramSocketImpl, socketGetOption, "(I)Ljava/lang/Object;"),
NATIVE_METHOD(PlainDatagramSocketImpl, socketSetOption0, "(ILjava/lang/Object;)V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, datagramSocketClose, "()V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, datagramSocketCreate, "()V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, receive0, "(Ljava/net/DatagramPacket;)V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, peekData, "(Ljava/net/DatagramPacket;)I"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, peek, "(Ljava/net/InetAddress;)I"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, send, "(Ljava/net/DatagramPacket;)V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, disconnect0, "(I)V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, connect0, "(Ljava/net/InetAddress;I)V"),
+ NATIVE_METHOD(PlainDatagramSocketImpl, bind0, "(ILjava/net/InetAddress;)V"),
NATIVE_METHOD(PlainDatagramSocketImpl, init, "()V"),
};
diff --git a/ojluni/src/main/native/Register.cpp b/ojluni/src/main/native/Register.cpp
index 99db9fa..8e767f7 100644
--- a/ojluni/src/main/native/Register.cpp
+++ b/ojluni/src/main/native/Register.cpp
@@ -27,7 +27,7 @@
#include <stdlib.h>
-#include <android/log.h>
+#include <log/log.h>
#include "JniConstants.h"
#include "ScopedLocalFrame.h"
diff --git a/ojluni/src/main/native/System.c b/ojluni/src/main/native/System.c
index 863a8c5..e75e01c 100644
--- a/ojluni/src/main/native/System.c
+++ b/ojluni/src/main/native/System.c
@@ -28,7 +28,7 @@
#include <string.h>
#include <time.h>
-#include <android/log.h>
+#include <log/log.h>
#include "io_util.h"
#include "jni.h"
diff --git a/openjdk_java_files.mk b/openjdk_java_files.mk
index 8c8700e..d27ef31 100644
--- a/openjdk_java_files.mk
+++ b/openjdk_java_files.mk
@@ -198,6 +198,7 @@
ojluni/src/main/java/java/lang/reflect/Type.java \
ojluni/src/main/java/java/lang/reflect/TypeVariable.java \
ojluni/src/main/java/java/lang/reflect/UndeclaredThrowableException.java \
+ ojluni/src/main/java/java/lang/reflect/WeakCache.java \
ojluni/src/main/java/java/lang/reflect/WildcardType.java \
ojluni/src/main/java/java/lang/reflect/package-info.java \
ojluni/src/main/java/java/lang/ref/PhantomReference.java \
diff --git a/support/src/test/java/libcore/java/security/StandardNames.java b/support/src/test/java/libcore/java/security/StandardNames.java
index 4c16601..b4bffc1 100644
--- a/support/src/test/java/libcore/java/security/StandardNames.java
+++ b/support/src/test/java/libcore/java/security/StandardNames.java
@@ -1021,8 +1021,7 @@
public static final List<String> ELLIPTIC_CURVES_DEFAULT = Arrays.asList(
"x25519 (29)",
"secp256r1 (23)",
- "secp384r1 (24)",
- "secp521r1 (25)"
+ "secp384r1 (24)"
);
private static final Set<String> PERMITTED_DEFAULT_KEY_EXCHANGE_ALGS =
diff --git a/tzdata/testing/src/main/libcore/tzdata/testing/ZoneInfoTestHelper.java b/tzdata/testing/src/main/libcore/tzdata/testing/ZoneInfoTestHelper.java
index a3b2a14..3cc3756 100644
--- a/tzdata/testing/src/main/libcore/tzdata/testing/ZoneInfoTestHelper.java
+++ b/tzdata/testing/src/main/libcore/tzdata/testing/ZoneInfoTestHelper.java
@@ -33,7 +33,7 @@
/**
* Constructs valid and invalid zic data for tests.
*/
- public static class ZoneInfoDataBuilder {
+ public static class ZicDataBuilder {
private int magic = 0x545a6966; // Default, valid magic.
private Integer transitionCountOverride; // Used to override the correct transition count.
@@ -43,19 +43,19 @@
private int[] isDsts; // Whether a type uses DST, one per type.
private int[] offsetsSeconds; // The UTC offset, one per type.
- public ZoneInfoDataBuilder() {}
+ public ZicDataBuilder() {}
- public ZoneInfoDataBuilder setMagic(int magic) {
+ public ZicDataBuilder setMagic(int magic) {
this.magic = magic;
return this;
}
- public ZoneInfoDataBuilder setTypeCountOverride(int typesCountOverride) {
+ public ZicDataBuilder setTypeCountOverride(int typesCountOverride) {
this.typesCountOverride = typesCountOverride;
return this;
}
- public ZoneInfoDataBuilder setTransitionCountOverride(int transitionCountOverride) {
+ public ZicDataBuilder setTransitionCountOverride(int transitionCountOverride) {
this.transitionCountOverride = transitionCountOverride;
return this;
}
@@ -63,7 +63,7 @@
/**
* See {@link #setTransitions(int[][])} and {@link #setTypes(int[][])}.
*/
- public ZoneInfoDataBuilder setTransitionsAndTypes(
+ public ZicDataBuilder setTransitionsAndTypes(
int[][] transitionPairs, int[][] typePairs) {
setTransitions(transitionPairs);
setTypes(typePairs);
@@ -77,7 +77,7 @@
* { transitionTimeSeconds2, typeIndex1 },
* }
*/
- public ZoneInfoDataBuilder setTransitions(int[][] transitionPairs) {
+ public ZicDataBuilder setTransitions(int[][] transitionPairs) {
int[] transitions = new int[transitionPairs.length];
byte[] types = new byte[transitionPairs.length];
for (int i = 0; i < transitionPairs.length; i++) {
@@ -97,7 +97,7 @@
* { typeIsDst2, offsetSeconds2 },
* }
*/
- public ZoneInfoDataBuilder setTypes(int[][] typePairs) {
+ public ZicDataBuilder setTypes(int[][] typePairs) {
int[] isDsts = new int[typePairs.length];
int[] offsetSeconds = new int[typePairs.length];
for (int i = 0; i < typePairs.length; i++) {
@@ -110,7 +110,7 @@
}
/** Initializes to a minimum viable ZoneInfo data. */
- public ZoneInfoDataBuilder initializeToValid() {
+ public ZicDataBuilder initializeToValid() {
setTransitions(new int[0][0]);
setTypes(new int[][] {
{ 3600, 0}
@@ -159,7 +159,7 @@
}
/**
- * Constructs valid and invalid TzData files for tests. See also ZoneCompactor class in
+ * Constructs valid and invalid tzdata files for tests. See also ZoneCompactor class in
* external/icu which is the real thing.
*/
public static class TzDataBuilder {
@@ -212,7 +212,7 @@
public TzDataBuilder initializeToValid() {
setHeaderMagic("tzdata9999a");
- addZicData("Europe/Elbonia", new ZoneInfoDataBuilder().initializeToValid().build());
+ addZicData("Europe/Elbonia", new ZicDataBuilder().initializeToValid().build());
setZoneTab("ZoneTab data");
return this;
}
diff --git a/tzdata/tools2/createTimeZoneBundle.sh b/tzdata/tools2/createTimeZoneBundle.sh
new file mode 100755
index 0000000..664fd81
--- /dev/null
+++ b/tzdata/tools2/createTimeZoneBundle.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# A script to generate TZ data updates.
+#
+# Usage: ./createTimeZoneBundle.sh <tzupdate.properties file> <output file>
+# See libcore.tzdata.update2.tools.CreateTimeZoneBundle for more information.
+
+TOOLS_DIR=src/main/libcore/tzdata/update2/tools
+UPDATE_DIR=../update2/src/main/libcore/tzdata/update2
+GEN_DIR=./gen
+
+# Fail if anything below fails
+set -e
+
+rm -rf ${GEN_DIR}
+mkdir -p ${GEN_DIR}
+
+javac \
+ ${TOOLS_DIR}/CreateTimeZoneBundle.java \
+ ${TOOLS_DIR}/TimeZoneBundleBuilder.java \
+ ${UPDATE_DIR}/BundleException.java \
+ ${UPDATE_DIR}/BundleVersion.java \
+ ${UPDATE_DIR}/FileUtils.java \
+ ${UPDATE_DIR}/TimeZoneBundle.java \
+ -d ${GEN_DIR}
+
+java -cp ${GEN_DIR} libcore.tzdata.update2.tools.CreateTimeZoneBundle $@
diff --git a/tzdata/tools2/createTzDataBundle.sh b/tzdata/tools2/createTzDataBundle.sh
deleted file mode 100755
index 2b97a61..0000000
--- a/tzdata/tools2/createTzDataBundle.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-
-# A script to generate TZ data updates.
-#
-# Usage: ./createTzDataBundle.sh <tzupdate.properties file> <output file>
-# See libcore.tzdata.update2.tools.CreateTzDataBundle for more information.
-
-TOOLS_DIR=src/main/libcore/tzdata/update2/tools
-UPDATE_DIR=../update2/src/main/libcore/tzdata/update2
-GEN_DIR=./gen
-
-# Fail if anything below fails
-set -e
-
-rm -rf ${GEN_DIR}
-mkdir -p ${GEN_DIR}
-
-javac \
- ${TOOLS_DIR}/CreateTzDataBundle.java \
- ${TOOLS_DIR}/TzDataBundleBuilder.java \
- ${UPDATE_DIR}/ConfigBundle.java \
- ${UPDATE_DIR}/FileUtils.java \
- -d ${GEN_DIR}
-
-java -cp ${GEN_DIR} libcore.tzdata.update2.tools.CreateTzDataBundle $@
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTzDataBundle.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneBundle.java
similarity index 79%
rename from tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTzDataBundle.java
rename to tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneBundle.java
index fa3a39c..6297663 100644
--- a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTzDataBundle.java
+++ b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/CreateTimeZoneBundle.java
@@ -23,18 +23,18 @@
import java.io.OutputStream;
import java.io.Reader;
import java.util.Properties;
-import libcore.tzdata.update2.ConfigBundle;
+import libcore.tzdata.update2.TimeZoneBundle;
/**
- * A command-line tool for creating a TZ data update bundle.
+ * A command-line tool for creating a timezone update bundle.
*
* Args:
* tzdata.properties file - the file describing the bundle (see template file in tzdata/tools)
* output file - the name of the file to be generated
*/
-public class CreateTzDataBundle {
+public class CreateTimeZoneBundle {
- private CreateTzDataBundle() {}
+ private CreateTimeZoneBundle() {}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
@@ -48,15 +48,16 @@
System.exit(2);
}
Properties p = loadProperties(f);
- TzDataBundleBuilder builder = new TzDataBundleBuilder()
- .setTzDataVersion(getMandatoryProperty(p, "tzdata.version"))
- .addBionicTzData(getMandatoryPropertyFile(p, "bionic.file"))
- .addIcuTzData(getMandatoryPropertyFile(p, "icu.file"));
+ TimeZoneBundleBuilder builder = new TimeZoneBundleBuilder()
+ .setRulesVersion(getMandatoryProperty(p, "rules.version"))
+ .setAndroidRevision(getMandatoryProperty(p, "android.revision"))
+ .setTzData(getMandatoryPropertyFile(p, "bionic.file"))
+ .setIcuData(getMandatoryPropertyFile(p, "icu.file"));
- ConfigBundle bundle = builder.build();
+ TimeZoneBundle bundle = builder.build();
File outputFile = new File(args[1]);
try (OutputStream os = new FileOutputStream(outputFile)) {
- os.write(bundle.getBundleBytes());
+ os.write(bundle.getBytes());
}
System.out.println("Wrote: " + outputFile);
}
@@ -93,7 +94,7 @@
private static void printUsage() {
System.out.println("Usage:");
- System.out.println("\t" + CreateTzDataBundle.class.getName() +
+ System.out.println("\t" + CreateTimeZoneBundle.class.getName() +
" <tzupdate.properties file> <output file>");
}
}
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneBundleBuilder.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneBundleBuilder.java
new file mode 100644
index 0000000..39aa496
--- /dev/null
+++ b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TimeZoneBundleBuilder.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 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.update2.tools;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import libcore.tzdata.update2.BundleException;
+import libcore.tzdata.update2.BundleVersion;
+import libcore.tzdata.update2.TimeZoneBundle;
+
+/**
+ * A class for creating a {@link TimeZoneBundle} containing timezone update data. Used in real
+ * bundle creation code and tests.
+ */
+public final class TimeZoneBundleBuilder {
+
+ private String bundleFormatVersion = BundleVersion.FULL_BUNDLE_FORMAT_VERSION;
+ private String rulesVersion;
+ private String androidRevision;
+ private byte[] tzData;
+ private byte[] icuData;
+
+ // For use in tests.
+ public TimeZoneBundleBuilder setBundleVersionForTests(String bundleVersion) {
+ this.bundleFormatVersion = bundleVersion;
+ return this;
+ }
+
+ public TimeZoneBundleBuilder setRulesVersion(String rulesVersion) {
+ this.rulesVersion = rulesVersion;
+ return this;
+ }
+
+ public TimeZoneBundleBuilder setAndroidRevision(String androidRevision) {
+ this.androidRevision = androidRevision;
+ return this;
+ }
+
+ public TimeZoneBundleBuilder clearVersionForTests() {
+ // This has the effect of omitting the version file in buildUnvalidated().
+ this.bundleFormatVersion = null;
+ return this;
+ }
+
+ public TimeZoneBundleBuilder setTzData(File tzDataFile) throws IOException {
+ return setTzData(readFileAsByteArray(tzDataFile));
+ }
+
+ public TimeZoneBundleBuilder setTzData(byte[] tzData) {
+ this.tzData = tzData;
+ return this;
+ }
+
+ // For use in tests.
+ public TimeZoneBundleBuilder clearTzDataForTests() {
+ this.tzData = null;
+ return this;
+ }
+
+ public TimeZoneBundleBuilder setIcuData(File icuDataFile) throws IOException {
+ return setIcuData(readFileAsByteArray(icuDataFile));
+ }
+
+ public TimeZoneBundleBuilder setIcuData(byte[] icuData) {
+ this.icuData = icuData;
+ return this;
+ }
+
+ // For use in tests.
+ public TimeZoneBundleBuilder clearIcuDataForTests() {
+ this.icuData = null;
+ return this;
+ }
+
+ /**
+ * For use in tests. Use {@link #build()}.
+ */
+ public TimeZoneBundle buildUnvalidated() throws BundleException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (ZipOutputStream zos = new ZipOutputStream(baos)) {
+ if (bundleFormatVersion != null && rulesVersion != null && androidRevision != null) {
+ BundleVersion bundleVersion =
+ new BundleVersion(bundleFormatVersion, rulesVersion, androidRevision);
+ addZipEntry(zos, TimeZoneBundle.BUNDLE_VERSION_FILE_NAME, bundleVersion.getBytes());
+ }
+
+ if (tzData != null) {
+ addZipEntry(zos, TimeZoneBundle.TZDATA_FILE_NAME, tzData);
+ }
+ if (icuData != null) {
+ addZipEntry(zos, TimeZoneBundle.ICU_DATA_FILE_NAME, icuData);
+ }
+ } catch (IOException e) {
+ throw new BundleException("Unable to create zip file", e);
+ }
+ return new TimeZoneBundle(baos.toByteArray());
+ }
+
+ /**
+ * Builds a {@link TimeZoneBundle}.
+ */
+ public TimeZoneBundle build() throws BundleException {
+ if (bundleFormatVersion == null) {
+ throw new IllegalStateException("Missing bundleVersion");
+ }
+ if (!BundleVersion.BUNDLE_FORMAT_VERSION_PATTERN.matcher(bundleFormatVersion).matches()) {
+ throw new IllegalStateException("bundleVersion invalid: " + bundleFormatVersion);
+ }
+
+ if (rulesVersion == null) {
+ throw new IllegalStateException("Missing rulesVersion");
+ }
+ if (!BundleVersion.RULES_VERSION_PATTERN.matcher(rulesVersion).matches()) {
+ throw new IllegalStateException("rulesVersion invalid: " + rulesVersion);
+ }
+
+ if (androidRevision == null) {
+ throw new IllegalStateException("Missing androidRevision");
+ }
+ if (!BundleVersion.ANDROID_REVISION_PATTERN.matcher(androidRevision).matches()) {
+ throw new IllegalStateException("androidRevision invalid: " + androidRevision);
+ }
+ if (icuData == null) {
+ throw new IllegalStateException("Missing icuData");
+ }
+ if (tzData == null) {
+ throw new IllegalStateException("Missing tzData");
+ }
+ return buildUnvalidated();
+ }
+
+ private static void addZipEntry(ZipOutputStream zos, String name, byte[] content)
+ throws BundleException {
+ try {
+ ZipEntry zipEntry = new ZipEntry(name);
+ zipEntry.setSize(content.length);
+ zos.putNextEntry(zipEntry);
+ zos.write(content);
+ zos.closeEntry();
+ } catch (IOException e) {
+ throw new BundleException("Unable to add zip entry", e);
+ }
+ }
+
+ /**
+ * Returns the contents of 'path' as a byte array.
+ */
+ public static byte[] readFileAsByteArray(File file) throws IOException {
+ byte[] buffer = new byte[8192];
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (FileInputStream fis = new FileInputStream(file)) {
+ int count;
+ while ((count = fis.read(buffer)) != -1) {
+ baos.write(buffer, 0, count);
+ }
+ }
+ return baos.toByteArray();
+ }
+}
+
diff --git a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TzDataBundleBuilder.java b/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TzDataBundleBuilder.java
deleted file mode 100644
index d13c197..0000000
--- a/tzdata/tools2/src/main/libcore/tzdata/update2/tools/TzDataBundleBuilder.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2015 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.update2.tools;
-
-import java.io.ByteArrayOutputStream;
-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.update2.ConfigBundle;
-
-/**
- * A class for creating a {@link ConfigBundle} containing timezone update data.
- */
-public final class TzDataBundleBuilder {
-
- private String tzDataVersion;
- private File zoneInfoFile;
- private File icuTzDataFile;
-
- public TzDataBundleBuilder setTzDataVersion(String tzDataVersion) {
- this.tzDataVersion = tzDataVersion;
- return this;
- }
-
- public TzDataBundleBuilder addBionicTzData(File zoneInfoFile) {
- this.zoneInfoFile = zoneInfoFile;
- return this;
- }
-
- public TzDataBundleBuilder addIcuTzData(File icuTzDataFile) {
- this.icuTzDataFile = icuTzDataFile;
- return this;
- }
-
- /**
- * Builds a {@link libcore.tzdata.update2.ConfigBundle}.
- */
- public ConfigBundle build() throws IOException {
- if (tzDataVersion == null) {
- throw new IllegalStateException("Missing tzDataVersion");
- }
- if (zoneInfoFile == null) {
- throw new IllegalStateException("Missing zoneInfo file");
- }
-
- return buildUnvalidated();
- }
-
- // For use in tests.
- public TzDataBundleBuilder clearBionicTzData() {
- this.zoneInfoFile = null;
- return this;
- }
-
- /**
- * For use in tests. Use {@link #build()}.
- */
- public ConfigBundle buildUnvalidated() throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try (ZipOutputStream zos = new ZipOutputStream(baos)) {
- addZipEntry(zos, ConfigBundle.BUNDLE_VERSION_FILE_NAME,
- ConfigBundle.BUNDLE_VERSION_BYTES);
- if (tzDataVersion != null) {
- addZipEntry(zos, ConfigBundle.TZ_DATA_VERSION_FILE_NAME,
- tzDataVersion.getBytes(StandardCharsets.UTF_8));
- }
- if (zoneInfoFile != null) {
- addZipEntry(zos, ConfigBundle.ZONEINFO_FILE_NAME,
- readFileAsByteArray(zoneInfoFile));
- }
- if (icuTzDataFile != null) {
- addZipEntry(zos, ConfigBundle.ICU_DATA_FILE_NAME,
- readFileAsByteArray(icuTzDataFile));
- }
- }
- return new ConfigBundle(baos.toByteArray());
- }
-
- private static void addZipEntry(ZipOutputStream zos, String name, byte[] content)
- throws IOException {
- ZipEntry zipEntry = new ZipEntry(name);
- zipEntry.setSize(content.length);
- zos.putNextEntry(zipEntry);
- zos.write(content);
- zos.closeEntry();
- }
-
- /**
- * Returns the contents of 'path' as a byte array.
- */
- public static byte[] readFileAsByteArray(File file) throws IOException {
- byte[] buffer = new byte[8192];
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try (FileInputStream fis = new FileInputStream(file)) {
- int count;
- while ((count = fis.read(buffer)) != -1) {
- baos.write(buffer, 0, count);
- }
- }
- return baos.toByteArray();
- }
-}
-
diff --git a/tzdata/tools2/testing/prepareTzDataUpdates.sh b/tzdata/tools2/testing/prepareTzDataUpdates.sh
new file mode 100755
index 0000000..99cd60f
--- /dev/null
+++ b/tzdata/tools2/testing/prepareTzDataUpdates.sh
@@ -0,0 +1,170 @@
+#!/bin/bash
+
+# Used as part of manual testing of tzdata update mechanism.
+
+if [[ -z "${ANDROID_BUILD_TOP}" ]]; then
+ echo "Configure your environment with build/envsetup.sh and lunch"
+ exit 1
+fi
+
+# Fail if anything below fails
+set -e
+
+cd $ANDROID_BUILD_TOP
+
+# Get the ICU version by looking at the stubdata file
+ICU_STUB_FILE=$(ls external/icu/icu4c/source/stubdata/icudt*l.dat)
+if [ $(echo "$ICU_STUB_FILE" | wc -l) -ne 1 ]; then
+ echo "Could not find unique ICU version from external/icu/icu4c/source/stubdata/icudt*l.dat"
+ exit 1
+fi
+
+ICU_VERSION=${ICU_STUB_FILE##*icudt}
+ICU_VERSION=${ICU_VERSION%l.dat}
+
+echo "Current ICU version is ${ICU_VERSION}"
+
+# Get the current tzdata version and find both the previous and new versions.
+TZDATA=libc/zoneinfo/tzdata
+
+TZHEADER=$(head -n1 bionic/$TZDATA | cut -c1-11)
+
+TZ_CURRENT=${TZHEADER:6}
+
+let currentYear=${TZ_CURRENT:0:4}
+update=${TZ_CURRENT:4:1}
+
+let currentUpdate=$(LC_CTYPE=C printf '%d' "'$update")
+let previousUpdate=currentUpdate-1
+if [ $previousUpdate -lt 97 ]; then
+ let previousYear=currentYear-1
+ PREVIOUSCOMMIT=$(cd bionic; git log --format=oneline ${TZDATA} | grep -E "${previousYear}.$" | head -n1)
+ TZ_PREVIOUS=$(echo $PREVIOUSCOMMIT | sed "s|.*\(${previousYear}.\)\$|\1|")
+else
+ TZ_PREVIOUS=${currentYear}$(printf "\\$(printf '%03o' "${previousUpdate}")")
+ PREVIOUSCOMMIT=$(cd bionic; git log --format=oneline ${TZDATA} | grep -E "${TZ_PREVIOUS}$" | head -n1)
+fi
+
+let nextUpdate=currentUpdate+1
+TZ_NEXT=${currentYear}$(printf "\\$(printf '%03o' "${nextUpdate}")")
+
+echo "Current version of bionic/${TZDATA} is ${TZ_CURRENT}"
+echo "Previous version of bionic/${TZDATA} is ${TZ_PREVIOUS} from commit:"
+echo " ${PREVIOUSCOMMIT}"
+echo "Next version of bionic/${TZDATA} is ${TZ_NEXT}"
+
+TMP=$(mktemp -d)
+
+TZ_PREVIOUS_SHA=${PREVIOUSCOMMIT%% *}
+
+TMP_PREVIOUS=${TMP}/${TZ_PREVIOUS}_test
+mkdir -p ${TMP_PREVIOUS}
+
+echo "Copied ${TZ_PREVIOUS} tzdata to ${TMP_PREVIOUS}"
+(cd bionic; git show ${TZ_PREVIOUS_SHA}:${TZDATA} > ${TMP_PREVIOUS}/tzdata)
+
+cd libcore/tzdata/tools2
+
+################################################################
+# Preparing previous update version.
+################################################################
+
+# Create an icu_tzdata.dat for the previous tzdata version.
+# Download the archive for the previous tzdata version.
+PREVIOUS_TAR_GZ="tzdata${TZ_PREVIOUS}.tar.gz"
+TMP_PREVIOUS_TAR_GZ=${TMP}/${PREVIOUS_TAR_GZ}
+IANA_PREVIOUS_URL="ftp://ftp.iana.org/tz/releases/${PREVIOUS_TAR_GZ}"
+echo "Downloading archive for ${TZ_PREVIOUS} version from ${IANA_PREVIOUS_URL}"
+wget -O ${TMP_PREVIOUS_TAR_GZ} ftp://ftp.iana.org/tz/releases/tzdata2016d.tar.gz -o ${TMP}/${PREVIOUS_TAR_GZ}.log
+
+ICU_UPDATE_RESOURCES_LOG=${TMP}/icuUpdateResources.log
+echo "Creating icu_tzdata.dat file for ${TZ_PREVIOUS}/ICU ${ICU_VERSION}, may take a while, check ${ICU_UPDATE_RESOURCES_LOG} for details"
+./createIcuUpdateResources.sh ${TMP_PREVIOUS_TAR_GZ} ${ICU_VERSION} &> ${ICU_UPDATE_RESOURCES_LOG}
+mv icu_tzdata.dat ${TMP_PREVIOUS}
+
+TZ_PREVIOUS_UPDATE_PROPERTIES=${TMP}/tzupdate.properties.${TZ_PREVIOUS}
+cat > ${TZ_PREVIOUS_UPDATE_PROPERTIES} <<EOF
+android.revision=001
+rules.version=${TZ_PREVIOUS}
+bionic.file=${TMP_PREVIOUS}/tzdata
+icu.file=${TMP_PREVIOUS}/icu_tzdata.dat
+EOF
+
+TZ_PREVIOUS_UPDATE_ZIP=update_${TZ_PREVIOUS}_test.zip
+./createTimeZoneBundle.sh ${TZ_PREVIOUS_UPDATE_PROPERTIES} ${TMP}/update_${TZ_PREVIOUS}_test.zip
+adb push ${TMP}/${TZ_PREVIOUS_UPDATE_ZIP} /data/local/tmp
+echo "Pushed ${TZ_PREVIOUS_UPDATE_ZIP} to /data/local/tmp"
+
+################################################################
+# Preparing current update version.
+################################################################
+
+# Replace the version number in the current tzdata to create the current version
+TMP_CURRENT=${TMP}/${TZ_CURRENT}_test
+mkdir -p ${TMP_CURRENT}
+sed "1s/^tzdata${TZ_PREVIOUS}/tzdata${TZ_NEXT}/" ${TMP_PREVIOUS}/tzdata > ${TMP_CURRENT}/tzdata
+echo "Transformed version ${TZ_PREVIOUS} of tzdata into version ${TZ_CURRENT}"
+
+# Replace the version number in the previous icu_tzdata to create the next version
+SEARCH=$(echo ${TZ_PREVIOUS} | sed "s/\(.\)/\1\\\x00/g")
+REPLACE=$(echo ${TZ_CURRENT} | sed "s/\(.\)/\1\\\x00/g")
+sed "s/$SEARCH/$REPLACE/" ${TMP_PREVIOUS}/icu_tzdata.dat > ${TMP_CURRENT}/icu_tzdata.dat
+echo "Transformed version ${TZ_PREVIOUS} of icu_tzdata into version ${TZ_CURRENT}"
+
+TZ_CURRENT_UPDATE_PROPERTIES=${TMP}/tzupdate.properties.${TZ_CURRENT}
+cat > ${TZ_CURRENT_UPDATE_PROPERTIES} <<EOF
+android.revision=001
+rules.version=${TZ_CURRENT}
+bionic.file=${TMP_CURRENT}/tzdata
+icu.file=${TMP_CURRENT}/icu_tzdata.dat
+EOF
+
+TZ_CURRENT_UPDATE_ZIP=update_${TZ_CURRENT}_test.zip
+./createTimeZoneBundle.sh ${TZ_CURRENT_UPDATE_PROPERTIES} ${TMP}/update_${TZ_CURRENT}_test.zip
+adb push ${TMP}/${TZ_CURRENT_UPDATE_ZIP} /data/local/tmp
+echo "Pushed ${TZ_CURRENT_UPDATE_ZIP} to /data/local/tmp"
+
+################################################################
+# Preparing next update version.
+################################################################
+
+# Replace the version number in the previous tzdata to create the next version
+TMP_NEXT=${TMP}/${TZ_NEXT}_test
+mkdir -p ${TMP_NEXT}
+sed "1s/^tzdata${TZ_PREVIOUS}/tzdata${TZ_NEXT}/" ${TMP_PREVIOUS}/tzdata > ${TMP_NEXT}/tzdata
+echo "Transformed version ${TZ_PREVIOUS} of tzdata into version ${TZ_NEXT}"
+
+# Replace the version number in the previous icu_tzdata to create the next version
+SEARCH=$(echo ${TZ_PREVIOUS} | sed "s/\(.\)/\1\\\x00/g")
+REPLACE=$(echo ${TZ_NEXT} | sed "s/\(.\)/\1\\\x00/g")
+sed "s/$SEARCH/$REPLACE/" ${TMP_PREVIOUS}/icu_tzdata.dat > ${TMP_NEXT}/icu_tzdata.dat
+echo "Transformed version ${TZ_PREVIOUS} of icu_tzdata into version ${TZ_NEXT}"
+
+TZ_NEXT_UPDATE_PROPERTIES=${TMP}/tzupdate.properties.${TZ_NEXT}
+cat > ${TZ_NEXT_UPDATE_PROPERTIES} <<EOF
+android.revision=001
+rules.version=${TZ_NEXT}
+bionic.file=${TMP_NEXT}/tzdata
+icu.file=${TMP_NEXT}/icu_tzdata.dat
+EOF
+
+TZ_NEXT_UPDATE_ZIP=update_${TZ_NEXT}_test.zip
+./createTimeZoneBundle.sh ${TZ_NEXT_UPDATE_PROPERTIES} ${TMP}/update_${TZ_NEXT}_test.zip
+adb push ${TMP}/${TZ_NEXT_UPDATE_ZIP} /data/local/tmp
+echo "Pushed ${TZ_NEXT_UPDATE_ZIP} to /data/local/tmp"
+
+################################################################
+# Preparing UpdateTestApp
+################################################################
+
+UPDATE_TEST_APP_LOG=${TMP}/updateTestApp.log
+echo "Building and installing UpdateTestApp, check ${UPDATE_TEST_APP_LOG}"
+cd $ANDROID_BUILD_TOP
+make -j -l8 UpdateTestApp &> ${UPDATE_TEST_APP_LOG}
+adb install -r ${ANDROID_PRODUCT_OUT}/data/app/UpdateTestApp/UpdateTestApp.apk &>> ${UPDATE_TEST_APP_LOG}
+
+echo
+echo "Paste the following into your shell"
+echo "OLD_TZUPDATE=${TMP}/${TZ_PREVIOUS_UPDATE_ZIP}"
+echo "CURRENT_TZUPDATE=${TMP}/${TZ_CURRENT_UPDATE_ZIP}"
+echo "NEW_TZUPDATE=${TMP}/${TZ_NEXT_UPDATE_ZIP}"
diff --git a/tzdata/tools2/testing/rebootAndGrabLogs.sh b/tzdata/tools2/testing/rebootAndGrabLogs.sh
new file mode 100755
index 0000000..a85481f
--- /dev/null
+++ b/tzdata/tools2/testing/rebootAndGrabLogs.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Used as part of manual testing of tzdata update mechanism.
+
+PREFIX=${1-bootlogs}
+
+echo "Rebooting...."
+adb reboot && adb wait-for-device
+
+TIME=${2-5}
+LOGCAT=${PREFIX}.logcat
+echo "Dumping logcat output in ${LOGCAT}, waiting for ${TIME} seconds"
+adb logcat > ${LOGCAT} 2>/dev/null &
+LOGCAT_PID=$!
+
+sleep ${TIME}
+
+# Kill the logcat process and wait, suppresses the Terminated message
+# output by the shell.
+kill ${LOGCAT_PID}
+wait ${LOGCAT_PID} 2>/dev/null
+
+DMESG=${PREFIX}.dmesg
+echo "Dumping dmesg output in ${DMESG}"
+adb shell dmesg > ${DMESG}
diff --git a/tzdata/tools2/tzupdate.properties b/tzdata/tools2/tzupdate.properties
index 9dd9467..f46d942 100644
--- a/tzdata/tools2/tzupdate.properties
+++ b/tzdata/tools2/tzupdate.properties
@@ -1,9 +1,11 @@
# Edit these to reflect the update files.
-# This should be the tzdata version. e.g. "2015a". Lexicographical sort order
-# may become important in future so if inventing interim releases only add
-# characters to the end.
-tzdata.version=
+# This should be the tzdata version. e.g. "2015a".
+rules.version=
+
+# This is used to indicate when Android has issued a revision. e.g. "001", "002".
+android.revision=001
+
bionic.file=
icu.file=
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/BundleException.java b/tzdata/update2/src/main/libcore/tzdata/update2/BundleException.java
new file mode 100644
index 0000000..72fb773
--- /dev/null
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/BundleException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.tzdata.update2;
+
+/**
+ * A checked exception used in connection with time zone bundle creation / installation.
+ */
+public class BundleException extends Exception {
+
+ public BundleException(String message) {
+ super(message);
+ }
+
+ public BundleException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/BundleVersion.java b/tzdata/update2/src/main/libcore/tzdata/update2/BundleVersion.java
new file mode 100644
index 0000000..6a366da
--- /dev/null
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/BundleVersion.java
@@ -0,0 +1,145 @@
+/*
+ * 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.tzdata.update2;
+
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Pattern;
+
+/**
+ * Constants and logic associated with the timezone bundle version file.
+ */
+public class BundleVersion {
+
+ /**
+ * The current bundle format version in the form XXX.YYY. Increment the first number (XXX)
+ * when making incompatible changes to the bundle structure, or the files contained within.
+ * The second number (YYY) is currently ignored.
+ */
+ public static final String BUNDLE_FORMAT_MAJOR_VERSION = "001";
+
+ public static final String FULL_BUNDLE_FORMAT_VERSION = BUNDLE_FORMAT_MAJOR_VERSION + ".001";
+
+ private static final int BUNDLE_FORMAT_LENGTH = 7;
+
+ public static final Pattern BUNDLE_FORMAT_VERSION_PATTERN = Pattern.compile("\\d{3}\\.\\d{3}");
+
+ /** A pattern that matches the IANA rules value of a rules update. e.g. "2016g" */
+ public static final Pattern RULES_VERSION_PATTERN = Pattern.compile("\\d{4}\\w");
+
+ private static final int RULES_VERSION_LENGTH = 5;
+
+ /** A pattern that matches the Android revision of a rules update. e.g. "001" */
+ public static final Pattern ANDROID_REVISION_PATTERN = Pattern.compile("\\d{3}");
+
+ private static final int ANDROID_REVISION_LENGTH = 3;
+
+ /**
+ * The length of a well-formed bundle version file:
+ * {Bundle version}|{Rule version}|{Android revision}
+ */
+ public static final int BUNDLE_VERSION_FILE_LENGTH = BUNDLE_FORMAT_LENGTH + 1
+ + RULES_VERSION_LENGTH
+ + 1 + ANDROID_REVISION_LENGTH;
+
+ private static final Pattern BUNDLE_VERSION_PATTERN = Pattern.compile(
+ BUNDLE_FORMAT_VERSION_PATTERN.pattern() + "\\|"
+ + RULES_VERSION_PATTERN.pattern() + "\\|"
+ + ANDROID_REVISION_PATTERN.pattern()
+ + ".*" /* ignore trailing */);
+
+ private final String bundleFormatVersion;
+
+ public final String rulesVersion;
+
+ public final String androidRevision;
+
+ public BundleVersion(String bundleFormatVersion, String rulesVersion,
+ String androidRevision) throws BundleException {
+ if (!BUNDLE_FORMAT_VERSION_PATTERN.matcher(bundleFormatVersion).matches()) {
+ throw new BundleException("Invalid bundleFormatVersion: " + bundleFormatVersion);
+ }
+ if (!RULES_VERSION_PATTERN.matcher(rulesVersion).matches()) {
+ throw new BundleException("Invalid rulesVersion: " + rulesVersion);
+ }
+ if (!ANDROID_REVISION_PATTERN.matcher(androidRevision).matches()) {
+ throw new BundleException("Invalid androidRevision: " + androidRevision);
+ }
+ this.bundleFormatVersion = bundleFormatVersion;
+ this.rulesVersion = rulesVersion;
+ this.androidRevision = androidRevision;
+ }
+
+ public static BundleVersion extractFromBytes(byte[] bytes) throws BundleException {
+ String bundleVersion = new String(bytes, StandardCharsets.US_ASCII);
+ try {
+ if (!BUNDLE_VERSION_PATTERN.matcher(bundleVersion).matches()) {
+ throw new BundleException("Invalid bundle version string: " + bundleVersion);
+ }
+ String bundleFormatVersion = bundleVersion.substring(0, 7);
+ String rulesVersion = bundleVersion.substring(8, 13);
+ String androidRevision = bundleVersion.substring(14);
+ return new BundleVersion(bundleFormatVersion, rulesVersion, androidRevision);
+ } catch (IndexOutOfBoundsException e) {
+ // The use of the regexp above should make this impossible.
+ throw new BundleException("Bundle version string too short:" + bundleVersion);
+ }
+ }
+
+ public String getBundleFormatMajorVersion() {
+ return bundleFormatVersion.substring(0, 3);
+ }
+
+ public byte[] getBytes() {
+ return getBytes(bundleFormatVersion, rulesVersion, androidRevision);
+ }
+
+ // @VisibleForTesting - can be used to construct invalid bundle version bytes.
+ public static byte[] getBytes(
+ String bundleFormatVersion, String rulesVersion, String androidRevision) {
+ return (bundleFormatVersion + "|" + rulesVersion + "|" + androidRevision)
+ .getBytes(StandardCharsets.US_ASCII);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ BundleVersion that = (BundleVersion) o;
+
+ if (!bundleFormatVersion.equals(that.bundleFormatVersion)) {
+ return false;
+ }
+ if (!rulesVersion.equals(that.rulesVersion)) {
+ return false;
+ }
+ return androidRevision.equals(that.androidRevision);
+ }
+
+ @Override
+ public String toString() {
+ return "BundleVersion{" +
+ "bundleFormatVersion='" + bundleFormatVersion + '\'' +
+ ", rulesVersion='" + rulesVersion + '\'' +
+ ", androidRevision='" + androidRevision + '\'' +
+ '}';
+ }
+}
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/FileUtils.java b/tzdata/update2/src/main/libcore/tzdata/update2/FileUtils.java
index 3a8515a..3d9a73a 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/FileUtils.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/FileUtils.java
@@ -146,7 +146,7 @@
FileUtils.doDelete(toDelete);
}
- public static boolean filesExist(File rootDir, String... fileNames) throws IOException {
+ public static boolean filesExist(File rootDir, String... fileNames) {
for (String fileName : fileNames) {
File file = new File(rootDir, fileName);
if (!file.exists()) {
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/ConfigBundle.java b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundle.java
similarity index 63%
rename from tzdata/update2/src/main/libcore/tzdata/update2/ConfigBundle.java
rename to tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundle.java
index e4363d4..6d11469 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/ConfigBundle.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundle.java
@@ -16,61 +16,87 @@
package libcore.tzdata.update2;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
- * A configuration bundle. This is a thin wrapper around some in-memory bytes representing a zip
+ * A timezone bundle. This is a thin wrapper around some in-memory bytes representing a zip
* archive and logic for its safe extraction.
*/
-public final class ConfigBundle {
-
- /**
- * The name of the file inside the bundle containing the bundle format version.
- * The content is ASCII bytes representing a version number. The number is
- * incremented when any of the other contents of the bundle changes in a way that is not
- * backwards compatible.
- */
- public static final String BUNDLE_VERSION_FILE_NAME = "bundle_version";
-
- /**
- * The current bundle version. Increment this number when making incompatible changes to the
- * bundle structure, or the files contained within.
- */
- public static final byte[] BUNDLE_VERSION_BYTES = "1".getBytes(StandardCharsets.US_ASCII);
-
- /**
- * The name of the file inside the bundle containing the TZ data version.
- * The content is ASCII bytes representing a version derived from the IANA rules version the
- * bundle is based on with an Android-specific suffix to denote minor versioning. e.g.
- * "2016g 001"
- */
- public static final String TZ_DATA_VERSION_FILE_NAME = "tzdata_version";
+public final class TimeZoneBundle {
/** The name of the file inside the bundle containing bionic/libcore TZ data. */
- public static final String ZONEINFO_FILE_NAME = "tzdata";
+ public static final String TZDATA_FILE_NAME = "tzdata";
/** The name of the file inside the bundle containing ICU TZ data. */
public static final String ICU_DATA_FILE_NAME = "icu/icu_tzdata.dat";
+ /**
+ * The name of the file inside the bundle containing the bundle version information.
+ * The content is ASCII bytes representing a set of version numbers. See {@link BundleVersion}.
+ */
+ public static final String BUNDLE_VERSION_FILE_NAME = "bundle_version";
+
private static final int BUFFER_SIZE = 8192;
+ /**
+ * Maximum size of entry getEntryContents() will pull into a byte array. To avoid exhausting
+ * heap memory when encountering unexpectedly large entries. 128k should be enough for anyone.
+ */
+ private static final long MAX_GET_ENTRY_CONTENTS_SIZE = 128 * 1024;
+
private final byte[] bytes;
- public ConfigBundle(byte[] bytes) {
+ public TimeZoneBundle(byte[] bytes) {
this.bytes = bytes;
}
- public byte[] getBundleBytes() {
+ public byte[] getBytes() {
return bytes;
}
+ public BundleVersion getBundleVersion() throws BundleException, IOException {
+ byte[] contents = getEntryContents(
+ new ByteArrayInputStream(bytes), BUNDLE_VERSION_FILE_NAME);
+ if (contents == null) {
+ throw new BundleException("Bundle version file entry not found");
+ }
+ return BundleVersion.extractFromBytes(contents);
+ }
+
+ private static byte[] getEntryContents(InputStream is, String entryName) throws IOException {
+ try (ZipInputStream zipInputStream = new ZipInputStream(is)) {
+ ZipEntry entry;
+ while ((entry = zipInputStream.getNextEntry()) != null) {
+ String name = entry.getName();
+
+ if (!entryName.equals(name)) {
+ continue;
+ }
+ // Guard against massive entries consuming too much heap memory.
+ if (entry.getSize() > MAX_GET_ENTRY_CONTENTS_SIZE) {
+ throw new IOException("Entry " + entryName + " too large: " + entry.getSize());
+ }
+ byte[] buffer = new byte[BUFFER_SIZE];
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ int count;
+ while ((count = zipInputStream.read(buffer)) != -1) {
+ baos.write(buffer, 0, count);
+ }
+ return baos.toByteArray();
+ }
+ }
+ // Entry not found.
+ return null;
+ }
+ }
+
public void extractTo(File targetDir) throws IOException {
extractZipSafely(new ByteArrayInputStream(bytes), targetDir, true /* makeWorldReadable */);
}
@@ -128,7 +154,7 @@
return false;
}
- ConfigBundle that = (ConfigBundle) o;
+ TimeZoneBundle that = (TimeZoneBundle) o;
if (!Arrays.equals(bytes, that.bytes)) {
return false;
@@ -136,5 +162,4 @@
return true;
}
-
}
diff --git a/tzdata/update2/src/main/libcore/tzdata/update2/TzDataBundleInstaller.java b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundleInstaller.java
similarity index 63%
rename from tzdata/update2/src/main/libcore/tzdata/update2/TzDataBundleInstaller.java
rename to tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundleInstaller.java
index 1ffe860..601b8e2 100644
--- a/tzdata/update2/src/main/libcore/tzdata/update2/TzDataBundleInstaller.java
+++ b/tzdata/update2/src/main/libcore/tzdata/update2/TimeZoneBundleInstaller.java
@@ -18,16 +18,15 @@
import android.util.Slog;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
import libcore.util.ZoneInfoDB;
/**
* A bundle-validation / extraction class. Separate from the services code that uses it for easier
- * testing.
+ * testing. This class is not thread-safe: callers are expected to handle mutual exclusion.
*/
-public final class TzDataBundleInstaller {
+public final class TimeZoneBundleInstaller {
private static final String CURRENT_TZ_DATA_DIR_NAME = "current";
private static final String WORKING_DIR_NAME = "working";
@@ -39,7 +38,7 @@
private final File currentTzDataDir;
private final File workingDir;
- public TzDataBundleInstaller(String logTag, File systemTzDataFile, File installDir) {
+ public TimeZoneBundleInstaller(String logTag, File systemTzDataFile, File installDir) {
this.logTag = logTag;
this.systemTzDataFile = systemTzDataFile;
oldTzDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
@@ -66,7 +65,7 @@
* Install the supplied content.
*
* <p>Errors during unpacking or installation will throw an {@link IOException}.
- * If the content is invalid this method returns {@code false}.
+ * If the bundle 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 {
@@ -80,23 +79,34 @@
Slog.i(logTag, "Unpacking / verifying time zone update");
unpackBundle(content, workingDir);
try {
- if (!checkBundleVersion(workingDir)) {
- Slog.i(logTag, "Update not applied: Bundle format version is incorrect.");
+ BundleVersion bundleVersion;
+ try {
+ bundleVersion = readBundleVersion(workingDir);
+ } catch (BundleException e) {
+ Slog.i(logTag, "Invalid bundle version: " + e.getMessage());
return false;
}
- // This check should not fail if the bundle version check passes, but we're being
- // intentionally paranoid.
- if (!checkBundleFilesExist(workingDir)) {
- Slog.i(logTag, "Update not applied: Bundle is missing files");
+ if (bundleVersion == null) {
+ Slog.i(logTag, "Update not applied: Bundle version could not be loaded");
+ return false;
+ }
+ if (!checkBundleFormatVersion(bundleVersion)) {
+ Slog.i(logTag, "Update not applied: Bundle format version check failed: "
+ + bundleVersion);
return false;
}
- if (!checkBundleRulesNewerThanSystem(systemTzDataFile, workingDir)) {
+ if (!checkBundleDataFilesExist(workingDir)) {
+ Slog.i(logTag, "Update not applied: Bundle is missing required data file(s)");
+ return false;
+ }
+
+ if (!checkBundleRulesNewerThanSystem(systemTzDataFile, bundleVersion)) {
Slog.i(logTag, "Update not applied: Bundle rules version check failed");
return false;
}
- File zoneInfoFile = new File(workingDir, ConfigBundle.ZONEINFO_FILE_NAME);
+ File zoneInfoFile = new File(workingDir, TimeZoneBundle.TZDATA_FILE_NAME);
ZoneInfoDB.TzData tzData = ZoneInfoDB.TzData.loadTzData(zoneInfoFile.getPath());
if (tzData == null) {
Slog.i(logTag, "Update not applied: " + zoneInfoFile + " could not be loaded");
@@ -163,6 +173,30 @@
}
}
+ /**
+ * Reads the currently installed bundle version. Returns {@code null} if there is no bundle
+ * installed.
+ *
+ * @throws IOException if there was a problem reading data from /data
+ * @throws BundleException if there was a problem with the installed bundle format/structure
+ */
+ public BundleVersion getInstalledBundleVersion() throws BundleException, IOException {
+ if (!currentTzDataDir.exists()) {
+ return null;
+ }
+ return readBundleVersion(currentTzDataDir);
+ }
+
+ /**
+ * Reads the timezone rules version present in /system. i.e. the version that would be present
+ * after a factory reset.
+ *
+ * @throws IOException if there was a problem reading data
+ */
+ public String getSystemRulesVersion() throws IOException {
+ return readSystemRulesVersion(systemTzDataFile);
+ }
+
private void deleteBestEffort(File dir) {
if (dir.exists()) {
try {
@@ -176,64 +210,63 @@
private void unpackBundle(byte[] content, File targetDir) throws IOException {
Slog.i(logTag, "Unpacking update content to: " + targetDir);
- ConfigBundle bundle = new ConfigBundle(content);
+ TimeZoneBundle bundle = new TimeZoneBundle(content);
bundle.extractTo(targetDir);
}
- private boolean checkBundleFilesExist(File unpackedContentDir) throws IOException {
+ private boolean checkBundleDataFilesExist(File unpackedContentDir) throws IOException {
Slog.i(logTag, "Verifying bundle contents");
return FileUtils.filesExist(unpackedContentDir,
- ConfigBundle.TZ_DATA_VERSION_FILE_NAME,
- ConfigBundle.ZONEINFO_FILE_NAME,
- ConfigBundle.ICU_DATA_FILE_NAME);
+ TimeZoneBundle.TZDATA_FILE_NAME,
+ TimeZoneBundle.ICU_DATA_FILE_NAME);
}
- private boolean checkBundleVersion(File unpackedContentDir) throws IOException {
- Slog.i(logTag, "Verifying bundle format version");
- if (!FileUtils.filesExist(unpackedContentDir, ConfigBundle.BUNDLE_VERSION_FILE_NAME)) {
- Slog.i(logTag, "Bundle format version file does not exist.");
- return false;
- }
-
+ private BundleVersion readBundleVersion(File bundleDir) throws BundleException, IOException {
+ Slog.i(logTag, "Reading bundle format version");
File bundleVersionFile =
- new File(unpackedContentDir, ConfigBundle.BUNDLE_VERSION_FILE_NAME);
- byte[] versionBytes = FileUtils.readBytes(bundleVersionFile, 10);
- if (!Arrays.equals(versionBytes, ConfigBundle.BUNDLE_VERSION_BYTES)) {
- Slog.i(logTag, "Incompatible bundle format version: " + Arrays.toString(versionBytes));
- return false;
+ new File(bundleDir, TimeZoneBundle.BUNDLE_VERSION_FILE_NAME);
+ if (!bundleVersionFile.exists()) {
+ throw new BundleException("No bundle version file found: " + bundleVersionFile);
}
- return true;
+ byte[] versionBytes =
+ FileUtils.readBytes(bundleVersionFile, BundleVersion.BUNDLE_VERSION_FILE_LENGTH);
+ return BundleVersion.extractFromBytes(versionBytes);
+ }
+
+ private boolean checkBundleFormatVersion(BundleVersion bundleVersion) {
+ return bundleVersion.getBundleFormatMajorVersion()
+ .equals(BundleVersion.BUNDLE_FORMAT_MAJOR_VERSION);
}
/**
* Returns true if the the bundle IANA rules version is >= system IANA rules version.
*/
private boolean checkBundleRulesNewerThanSystem(
- File systemTzDataFile, File unpackedContentDir) throws IOException {
- Slog.i(logTag, "Reading bundle rules version");
- File bundleRulesFile =
- new File(unpackedContentDir, ConfigBundle.TZ_DATA_VERSION_FILE_NAME);
- // Read the first 5 bytes of the bundle rules version file. It is expected to be longer but
- // should always starts with the IANA release version, e.g. 2016g.
- byte[] rulesBytes = FileUtils.readBytes(bundleRulesFile, 5 /* maxBytes */);
- if (rulesBytes.length != 5) {
- Slog.i(logTag, "Bundle rules version file too short.");
- return false;
- }
- // We don't inspect the rulesVersion and just assume it's valid ASCII containing the IANA
- // rules version: it should be in the form "20\d\d[a-zA-Z]", e.g. 2016g. If we got this far
- // (i.e. through a signature check) then it should be ok.
- String rulesVersion = new String(rulesBytes, StandardCharsets.US_ASCII);
+ File systemTzDataFile, BundleVersion bundleVersion) throws IOException {
// We only check the /system tzdata file and assume that other data like ICU is in sync.
// There is a CTS test that checks ICU and bionic/libcore are in sync.
Slog.i(logTag, "Reading /system rules version");
+ String systemRulesVersion = readSystemRulesVersion(systemTzDataFile);
+
+ String bundleRulesVersion = bundleVersion.rulesVersion;
+ // canApply = bundleRulesVersion >= systemRulesVersion
+ boolean canApply = bundleRulesVersion.compareTo(systemRulesVersion) >= 0;
+ if (!canApply) {
+ Slog.i(logTag, "Failed rules version check: bundleRulesVersion="
+ + bundleRulesVersion + ", systemRulesVersion=" + systemRulesVersion);
+ } else {
+ Slog.i(logTag, "Passed rules version check: bundleRulesVersion="
+ + bundleRulesVersion + ", systemRulesVersion=" + systemRulesVersion);
+ }
+ return canApply;
+ }
+
+ private String readSystemRulesVersion(File systemTzDataFile) throws IOException {
if (!systemTzDataFile.exists()) {
Slog.i(logTag, "tzdata file cannot be found in /system");
- return false;
+ throw new FileNotFoundException("system tzdata does not exist: " + systemTzDataFile);
}
- String systemRulesVersion = ZoneInfoDB.TzData.getRulesVersion(systemTzDataFile);
- // rulesVersion >= systemRulesVersion
- return rulesVersion.compareTo(systemRulesVersion) >= 0;
+ return ZoneInfoDB.TzData.getRulesVersion(systemTzDataFile);
}
}
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleInstallerTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleInstallerTest.java
new file mode 100644
index 0000000..0c7e52b
--- /dev/null
+++ b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleInstallerTest.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2015 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.update2;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+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;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+import libcore.tzdata.testing.ZoneInfoTestHelper;
+import libcore.tzdata.update2.tools.TimeZoneBundleBuilder;
+
+/**
+ * Tests for {@link TimeZoneBundleInstaller}.
+ */
+public class TimeZoneBundleInstallerTest extends TestCase {
+
+ // OLDER_RULES_VERSION < SYSTEM_RULES_VERSION < NEW_RULES_VERSION < NEWER_RULES_VERSION
+ private static final String OLDER_RULES_VERSION = "2030a";
+ private static final String SYSTEM_RULES_VERSION = "2030b";
+ private static final String NEW_RULES_VERSION = "2030c";
+ private static final String NEWER_RULES_VERSION = "2030d";
+
+ private TimeZoneBundleInstaller installer;
+ private File tempDir;
+ private File testInstallDir;
+ private File testSystemTzDataDir;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ tempDir = createDirectory("tempDir");
+ testInstallDir = createDirectory("testInstall");
+ testSystemTzDataDir = createDirectory("testSystemTzData");
+
+ // Create a file to represent the tzdata file in the /system partition of the device.
+ File testSystemTzDataFile = new File(testSystemTzDataDir, "tzdata");
+ byte[] systemTzDataBytes = createTzData(SYSTEM_RULES_VERSION);
+ createFile(testSystemTzDataFile, systemTzDataBytes);
+
+ installer = new TimeZoneBundleInstaller(
+ "TimeZoneBundleInstallerTest", testSystemTzDataFile, testInstallDir);
+ }
+
+ private static File createDirectory(String prefix) throws Exception {
+ File dir = File.createTempFile(prefix, "");
+ assertTrue(dir.delete());
+ assertTrue(dir.mkdir());
+ return dir;
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (testSystemTzDataDir.exists()) {
+ FileUtils.deleteRecursive(testInstallDir);
+ }
+ if (testInstallDir.exists()) {
+ FileUtils.deleteRecursive(testInstallDir);
+ }
+ if (tempDir.exists()) {
+ FileUtils.deleteRecursive(tempDir);
+ }
+ super.tearDown();
+ }
+
+ /** Tests the an update on a device will fail if the /system tzdata file cannot be found. */
+ public void testInstall_badSystemFile() throws Exception {
+ File doesNotExist = new File(testSystemTzDataDir, "doesNotExist");
+ TimeZoneBundleInstaller brokenSystemInstaller = new TimeZoneBundleInstaller(
+ "TimeZoneBundleInstallerTest", doesNotExist, testInstallDir);
+ TimeZoneBundle tzData = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+
+ try {
+ brokenSystemInstaller.install(tzData.getBytes());
+ fail();
+ } catch (IOException expected) {}
+
+ assertNoContentInstalled();
+ }
+
+ /** Tests the first successful update on a device */
+ public void testInstall_successfulFirstUpdate() throws Exception {
+ TimeZoneBundle bundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+
+ assertTrue(installer.install(bundle.getBytes()));
+ assertBundleInstalled(bundle);
+ }
+
+ /**
+ * Tests we can install an update the same version as is in /system.
+ */
+ public void testInstall_successfulFirstUpdate_sameVersionAsSystem() throws Exception {
+ TimeZoneBundle bundle = createValidTimeZoneBundle(SYSTEM_RULES_VERSION, "001");
+ assertTrue(installer.install(bundle.getBytes()));
+ assertBundleInstalled(bundle);
+ }
+
+ /**
+ * Tests we cannot install an update older than the version in /system.
+ */
+ public void testInstall_unsuccessfulFirstUpdate_olderVersionThanSystem() throws Exception {
+ TimeZoneBundle bundle = createValidTimeZoneBundle(OLDER_RULES_VERSION, "001");
+ assertFalse(installer.install(bundle.getBytes()));
+ assertNoContentInstalled();
+ }
+
+ /**
+ * Tests an update on a device when there is a prior update already applied.
+ */
+ public void testInstall_successfulFollowOnUpdate_newerVersion() throws Exception {
+ TimeZoneBundle bundle1 = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+ assertTrue(installer.install(bundle1.getBytes()));
+ assertBundleInstalled(bundle1);
+
+ TimeZoneBundle bundle2 = createValidTimeZoneBundle(NEW_RULES_VERSION, "002");
+ assertTrue(installer.install(bundle2.getBytes()));
+ assertBundleInstalled(bundle2);
+
+ TimeZoneBundle bundle3 = createValidTimeZoneBundle(NEWER_RULES_VERSION, "001");
+ assertTrue(installer.install(bundle3.getBytes()));
+ assertBundleInstalled(bundle3);
+ }
+
+ /**
+ * 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 {
+ TimeZoneBundle bundle1 = createValidTimeZoneBundle(NEW_RULES_VERSION, "002");
+ assertTrue(installer.install(bundle1.getBytes()));
+ assertBundleInstalled(bundle1);
+
+ TimeZoneBundle bundle2 = createValidTimeZoneBundle(OLDER_RULES_VERSION, "001");
+ assertFalse(installer.install(bundle2.getBytes()));
+ assertBundleInstalled(bundle1);
+ }
+
+ /** Tests that a bundle with a missing file will not update the content. */
+ public void testInstall_missingTzDataFile() throws Exception {
+ TimeZoneBundle installedBundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+ assertTrue(installer.install(installedBundle.getBytes()));
+ assertBundleInstalled(installedBundle);
+
+ TimeZoneBundle incompleteBundle =
+ createValidTimeZoneBundleBuilder(NEWER_RULES_VERSION, "001")
+ .clearTzDataForTests()
+ .buildUnvalidated();
+ assertFalse(installer.install(incompleteBundle.getBytes()));
+ assertBundleInstalled(installedBundle);
+ }
+
+ /** Tests that a bundle with a missing file will not update the content. */
+ public void testInstall_missingIcuFile() throws Exception {
+ TimeZoneBundle installedBundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+ assertTrue(installer.install(installedBundle.getBytes()));
+ assertBundleInstalled(installedBundle);
+
+ TimeZoneBundle incompleteBundle =
+ createValidTimeZoneBundleBuilder(NEWER_RULES_VERSION, "001")
+ .clearIcuDataForTests()
+ .buildUnvalidated();
+ assertFalse(installer.install(incompleteBundle.getBytes()));
+ assertBundleInstalled(installedBundle);
+ }
+
+ /**
+ * Tests that an update will be unpacked even if there is a partial update from a previous run.
+ */
+ public void testInstall_withWorkingDir() throws Exception {
+ File workingDir = installer.getWorkingDir();
+ assertTrue(workingDir.mkdir());
+ createFile(new File(workingDir, "myFile"), new byte[] { 'a' });
+
+ TimeZoneBundle bundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+ assertTrue(installer.install(bundle.getBytes()));
+ assertBundleInstalled(bundle);
+ }
+
+ /**
+ * Tests that a bundle without a bundle version file will be rejected.
+ */
+ public void testInstall_withMissingBundleVersionFile() throws Exception {
+ // Create a bundle without a version file.
+ TimeZoneBundle bundle = createValidTimeZoneBundleBuilder(NEW_RULES_VERSION, "001")
+ .clearVersionForTests()
+ .buildUnvalidated();
+ assertFalse(installer.install(bundle.getBytes()));
+ assertNoContentInstalled();
+ }
+
+ /**
+ * Tests that a bundle with an newer bundle version will be rejected.
+ */
+ public void testInstall_withNewerBundleVersion() throws Exception {
+ // Create a bundle that will appear to be newer than the one currently supported.
+ TimeZoneBundle bundle = createValidTimeZoneBundleBuilder(NEW_RULES_VERSION, "001")
+ .setBundleVersionForTests("002.001")
+ .buildUnvalidated();
+ assertFalse(installer.install(bundle.getBytes()));
+ assertNoContentInstalled();
+ }
+
+ /**
+ * Tests that a bundle with a badly formed bundle version will be rejected.
+ */
+ public void testInstall_withBadlyFormedBundleVersion() throws Exception {
+ // Create a bundle that has an invalid major bundle version. It should be 3 numeric
+ // characters, "." and 3 more numeric characters.
+ String invalidBundleVersion = "A01.001";
+ byte[] versionBytes =
+ BundleVersion.getBytes(invalidBundleVersion, NEW_RULES_VERSION, "001");
+ TimeZoneBundle bundle = createTimeZoneBundleWithVersionBytes(versionBytes);
+ assertFalse(installer.install(bundle.getBytes()));
+ assertNoContentInstalled();
+ }
+
+ /**
+ * Tests that a bundle with a badly formed android revision will be rejected.
+ */
+ public void testInstall_withBadlyFormedAndroidRevision() throws Exception {
+ // Create a bundle that has an invalid Android revision. It should be 3 numeric characters.
+ String invalidAndroidRevision = "A01";
+ byte[] versionBytes = BundleVersion.getBytes(BundleVersion.FULL_BUNDLE_FORMAT_VERSION,
+ NEW_RULES_VERSION, invalidAndroidRevision);
+ TimeZoneBundle bundle = createTimeZoneBundleWithVersionBytes(versionBytes);
+ assertFalse(installer.install(bundle.getBytes()));
+ assertNoContentInstalled();
+ }
+
+ /**
+ * Tests that a bundle with a badly formed android revision will be rejected.
+ */
+ public void testInstall_withBadlyFormedRulesVersion() throws Exception {
+ // Create a bundle that has an invalid rules version. It should be in the form "2016c".
+ final String invalidRulesVersion = "203Bc";
+ byte[] versionBytes = BundleVersion.getBytes(BundleVersion.FULL_BUNDLE_FORMAT_VERSION,
+ invalidRulesVersion, "001");
+ TimeZoneBundle bundle = createTimeZoneBundleWithVersionBytes(versionBytes);
+ assertFalse(installer.install(bundle.getBytes()));
+ assertNoContentInstalled();
+ }
+
+ public void testUninstall_noExistingDataBundle() throws Exception {
+ assertFalse(installer.uninstall());
+ assertNoContentInstalled();
+ }
+
+ public void testUninstall_existingDataBundle() throws Exception {
+ File currentDataDir = installer.getCurrentTzDataDir();
+ assertTrue(currentDataDir.mkdir());
+
+ assertTrue(installer.uninstall());
+ assertNoContentInstalled();
+ }
+
+ public void testUninstall_oldDirsAlreadyExists() throws Exception {
+ File oldTzDataDir = installer.getOldTzDataDir();
+ assertTrue(oldTzDataDir.mkdir());
+
+ File currentDataDir = installer.getCurrentTzDataDir();
+ assertTrue(currentDataDir.mkdir());
+
+ assertTrue(installer.uninstall());
+ assertNoContentInstalled();
+ }
+
+ public void testGetSystemRulesVersion() throws Exception {
+ assertEquals(SYSTEM_RULES_VERSION, installer.getSystemRulesVersion());
+ }
+
+ private static TimeZoneBundle createValidTimeZoneBundle(
+ String rulesVersion, String androidRevision) throws Exception {
+ return createValidTimeZoneBundleBuilder(rulesVersion, androidRevision).build();
+ }
+
+ private static TimeZoneBundleBuilder createValidTimeZoneBundleBuilder(
+ String rulesVersion, String androidRevision) throws Exception {
+
+ byte[] bionicTzData = createTzData(rulesVersion);
+ byte[] icuData = new byte[] { 'a' };
+
+ return new TimeZoneBundleBuilder()
+ .setRulesVersion(rulesVersion)
+ .setAndroidRevision(androidRevision)
+ .setTzData(bionicTzData)
+ .setIcuData(icuData);
+ }
+
+ private void assertBundleInstalled(TimeZoneBundle expectedBundle) throws Exception {
+ assertTrue(testInstallDir.exists());
+
+ File currentTzDataDir = installer.getCurrentTzDataDir();
+ assertTrue(currentTzDataDir.exists());
+
+ File bundleVersionFile =
+ new File(currentTzDataDir, TimeZoneBundle.BUNDLE_VERSION_FILE_NAME);
+ assertTrue(bundleVersionFile.exists());
+
+ File bionicFile = new File(currentTzDataDir, TimeZoneBundle.TZDATA_FILE_NAME);
+ assertTrue(bionicFile.exists());
+
+ File icuFile = new File(currentTzDataDir, TimeZoneBundle.ICU_DATA_FILE_NAME);
+ assertTrue(icuFile.exists());
+
+ // Assert getInstalledBundleVersion() is reporting correctly.
+ assertEquals(expectedBundle.getBundleVersion(), installer.getInstalledBundleVersion());
+
+ try (ZipInputStream zis = new ZipInputStream(
+ new ByteArrayInputStream(expectedBundle.getBytes()))) {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ String entryName = entry.getName();
+ File actualFile;
+ if (entryName.endsWith(TimeZoneBundle.BUNDLE_VERSION_FILE_NAME)) {
+ actualFile = bundleVersionFile;
+ } else if (entryName.endsWith(TimeZoneBundle.ICU_DATA_FILE_NAME)) {
+ actualFile = icuFile;
+ } else if (entryName.endsWith(TimeZoneBundle.TZDATA_FILE_NAME)) {
+ actualFile = bionicFile;
+ } else {
+ throw new AssertionFailedError("Unknown file found");
+ }
+ assertContentsMatches(zis, actualFile);
+ }
+ }
+
+ // Also check no working directory is left lying around.
+ File workingDir = installer.getWorkingDir();
+ assertFalse(workingDir.exists());
+ }
+
+ private void assertContentsMatches(InputStream expected, File actual)
+ throws Exception {
+ byte[] actualBytes = IoUtils.readFileAsByteArray(actual.getPath());
+ byte[] expectedBytes = Streams.readFullyNoClose(expected);
+ assertTrue(Arrays.equals(expectedBytes, actualBytes));
+ }
+
+ private void assertNoContentInstalled() throws Exception {
+ assertNull(installer.getInstalledBundleVersion());
+
+ File currentTzDataDir = installer.getCurrentTzDataDir();
+ assertFalse(currentTzDataDir.exists());
+
+ // Also check no working directories are left lying around.
+ File workingDir = installer.getWorkingDir();
+ assertFalse(workingDir.exists());
+
+ File oldDataDir = installer.getOldTzDataDir();
+ assertFalse(oldDataDir.exists());
+ }
+
+ private static byte[] createTzData(String rulesVersion) {
+ return new ZoneInfoTestHelper.TzDataBuilder()
+ .initializeToValid()
+ .setHeaderMagic("tzdata" + rulesVersion)
+ .build();
+ }
+
+ private static void createFile(File file, byte[] bytes) {
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(bytes);
+ } catch (IOException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Creates a TimeZoneBundle containing arbitrary bytes in the version file. Used for testing
+ * bundles with badly formed version info.
+ */
+ private static TimeZoneBundle createTimeZoneBundleWithVersionBytes(byte[] versionBytes)
+ throws Exception {
+
+ // Create a valid bundle, then manipulate the version file.
+ TimeZoneBundle bundle = createValidTimeZoneBundle(NEW_RULES_VERSION, "001");
+ byte[] bundleBytes = bundle.getBytes();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(bundleBytes.length);
+ try (ZipInputStream zipInputStream =
+ new ZipInputStream(new ByteArrayInputStream(bundleBytes));
+ ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
+
+ ZipEntry entry;
+ while ((entry = zipInputStream.getNextEntry()) != null) {
+ zipOutputStream.putNextEntry(entry);
+ if (entry.getName().equals(TimeZoneBundle.BUNDLE_VERSION_FILE_NAME)) {
+ // Replace the content.
+ zipOutputStream.write(versionBytes);
+ } else {
+ // Just copy the content.
+ Streams.copy(zipInputStream, zipOutputStream);
+ }
+ zipOutputStream.closeEntry();
+ zipInputStream.closeEntry();
+ }
+ }
+ return new TimeZoneBundle(baos.toByteArray());
+ }
+}
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/ConfigBundleTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleTest.java
similarity index 80%
rename from tzdata/update2/src/test/libcore/tzdata/update2/ConfigBundleTest.java
rename to tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleTest.java
index 509865f..6aaf199 100644
--- a/tzdata/update2/src/test/libcore/tzdata/update2/ConfigBundleTest.java
+++ b/tzdata/update2/src/test/libcore/tzdata/update2/TimeZoneBundleTest.java
@@ -23,6 +23,7 @@
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -31,9 +32,9 @@
import libcore.io.IoUtils;
/**
- * Tests for {@link ConfigBundle}.
+ * Tests for {@link TimeZoneBundle}.
*/
-public class ConfigBundleTest extends TestCase {
+public class TimeZoneBundleTest extends TestCase {
private final List<File> testFiles = new ArrayList<>();
@@ -47,6 +48,19 @@
super.tearDown();
}
+ public void testGetBundleVersion() throws Exception {
+ BundleVersion bundleVersion =
+ new BundleVersion(BundleVersion.FULL_BUNDLE_FORMAT_VERSION, "2016c", "001");
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
+ addZipEntry(zipOutputStream, TimeZoneBundle.BUNDLE_VERSION_FILE_NAME,
+ bundleVersion.getBytes());
+ }
+
+ TimeZoneBundle bundle = new TimeZoneBundle(baos.toByteArray());
+ assertEquals(bundleVersion, bundle.getBundleVersion());
+ }
+
public void testExtractZipSafely_goodZip() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
@@ -61,7 +75,7 @@
File targetDir = new File(dir, "target");
TestInputStream inputStream =
new TestInputStream(new ByteArrayInputStream(baos.toByteArray()));
- ConfigBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
+ TimeZoneBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
inputStream.assertClosed();
assertFilesExist(
new File(targetDir, "leadingSlash"),
@@ -95,7 +109,7 @@
TestInputStream inputStream = new TestInputStream(
new ByteArrayInputStream(baos.toByteArray()));
try {
- ConfigBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
+ TimeZoneBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
fail();
} catch (IOException expected) {
}
@@ -104,10 +118,15 @@
private static void addZipEntry(ZipOutputStream zipOutputStream, String name)
throws IOException {
+ addZipEntry(zipOutputStream, name, "a".getBytes(StandardCharsets.US_ASCII));
+ }
+
+ private static void addZipEntry(ZipOutputStream zipOutputStream, String name, byte[] content)
+ throws IOException {
ZipEntry zipEntry = new ZipEntry(name);
zipOutputStream.putNextEntry(zipEntry);
if (!zipEntry.isDirectory()) {
- zipOutputStream.write('a');
+ zipOutputStream.write(content);
}
}
diff --git a/tzdata/update2/src/test/libcore/tzdata/update2/TzDataBundleInstallerTest.java b/tzdata/update2/src/test/libcore/tzdata/update2/TzDataBundleInstallerTest.java
deleted file mode 100644
index 34afcf1..0000000
--- a/tzdata/update2/src/test/libcore/tzdata/update2/TzDataBundleInstallerTest.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2015 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.update2;
-
-import junit.framework.TestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-import libcore.io.Streams;
-import libcore.tzdata.testing.ZoneInfoTestHelper;
-import libcore.tzdata.update2.tools.TzDataBundleBuilder;
-
-/**
- * Tests for {@link TzDataBundleInstaller}.
- */
-public class TzDataBundleInstallerTest extends TestCase {
-
- // OLDER_RULES_VERSION < SYSTEM_RULES_VERSION < NEW_RULES_VERSION < NEWER_RULES_VERSION
- private static final String OLDER_RULES_VERSION = "2030a";
- private static final String SYSTEM_RULES_VERSION = "2030b";
- private static final String NEW_RULES_VERSION = "2030c";
- private static final String NEWER_RULES_VERSION = "2030d";
-
- private TzDataBundleInstaller installer;
- private File tempDir;
- private File testInstallDir;
- private File testSystemTzDataDir;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- tempDir = createDirectory("tempDir");
- testInstallDir = createDirectory("testInstall");
- testSystemTzDataDir = createDirectory("testSystemTzData");
-
- // Create a file to represent the tzdata file in the /system partition of the device.
- File testSystemTzDataFile = new File(testSystemTzDataDir, "tzdata");
- createTzDataFile(testSystemTzDataFile, SYSTEM_RULES_VERSION);
-
- installer = new TzDataBundleInstaller(
- "TzDataBundleInstallerTest", testSystemTzDataFile, testInstallDir);
- }
-
- private static File createDirectory(String prefix) throws IOException {
- File dir = File.createTempFile(prefix, "");
- assertTrue(dir.delete());
- assertTrue(dir.mkdir());
- return dir;
- }
-
- @Override
- public void tearDown() throws Exception {
- if (testSystemTzDataDir.exists()) {
- FileUtils.deleteRecursive(testInstallDir);
- }
- if (testInstallDir.exists()) {
- FileUtils.deleteRecursive(testInstallDir);
- }
- if (tempDir.exists()) {
- FileUtils.deleteRecursive(tempDir);
- }
- super.tearDown();
- }
-
- /** Tests the an update on a device will fail if the /system tzdata file cannot be found. */
- public void testBadSystemFile() throws Exception {
- File doesNotExist = new File(testSystemTzDataDir, "doesNotExist");
- TzDataBundleInstaller brokenSystemInstaller = new TzDataBundleInstaller(
- "TzDataBundleInstallerTest", doesNotExist, testInstallDir);
- ConfigBundle tzData = createValidTzDataBundle(NEW_RULES_VERSION);
-
- assertFalse(brokenSystemInstaller.install(tzData.getBundleBytes()));
- assertNoContentInstalled();
- }
-
- /** Tests the first successful update on a device */
- public void testSuccessfulFirstUpdate() throws Exception {
- ConfigBundle tzData = createValidTzDataBundle(NEW_RULES_VERSION);
-
- assertTrue(installer.install(tzData.getBundleBytes()));
- assertTzDataInstalled(tzData);
- }
-
- /**
- * Tests we can install an update the same version as is in /system.
- */
- public void testSuccessfulFirstUpdate_sameVersionAsSystem() throws Exception {
- ConfigBundle tzData1 = createValidTzDataBundle(SYSTEM_RULES_VERSION);
- assertTrue(installer.install(tzData1.getBundleBytes()));
- assertTzDataInstalled(tzData1);
- }
-
- /**
- * Tests we cannot install an update older than the version in /system.
- */
- public void testUnsuccessfulFirstUpdate_olderVersionThanSystem() throws Exception {
- ConfigBundle tzData1 = createValidTzDataBundle(OLDER_RULES_VERSION);
- assertFalse(installer.install(tzData1.getBundleBytes()));
- assertNoContentInstalled();
- }
-
- /**
- * Tests an update on a device when there is a prior update already applied.
- */
- public void testSuccessfulFollowOnUpdate_newerVersion() throws Exception {
- ConfigBundle tzData1 = createValidTzDataBundle(NEW_RULES_VERSION);
- assertTrue(installer.install(tzData1.getBundleBytes()));
- assertTzDataInstalled(tzData1);
-
- ConfigBundle tzData2 = createValidTzDataBundle(NEWER_RULES_VERSION);
- assertTrue(installer.install(tzData2.getBundleBytes()));
- assertTzDataInstalled(tzData2);
- }
-
- /**
- * 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 testUnsuccessfulFollowOnUpdate_olderVersion() throws Exception {
- ConfigBundle tzData1 = createValidTzDataBundle(NEW_RULES_VERSION);
- assertTrue(installer.install(tzData1.getBundleBytes()));
- assertTzDataInstalled(tzData1);
-
- ConfigBundle tzData2 = createValidTzDataBundle(OLDER_RULES_VERSION);
- assertFalse(installer.install(tzData2.getBundleBytes()));
- assertTzDataInstalled(tzData1);
- }
-
- /** Tests that a bundle with a missing file will not update the content. */
- public void testMissingRequiredBundleFile() throws Exception {
- ConfigBundle installedConfigBundle = createValidTzDataBundle(NEW_RULES_VERSION);
- assertTrue(installer.install(installedConfigBundle.getBundleBytes()));
- assertTzDataInstalled(installedConfigBundle);
-
- ConfigBundle incompleteUpdate =
- createValidTzDataBundleBuilder(NEWER_RULES_VERSION)
- .clearBionicTzData()
- .buildUnvalidated();
- assertFalse(installer.install(incompleteUpdate.getBundleBytes()));
- assertTzDataInstalled(installedConfigBundle);
- }
-
- /**
- * Tests that an update will be unpacked even if there is a partial update from a previous run.
- */
- public void testInstallWithWorkingDir() throws Exception {
- File workingDir = installer.getWorkingDir();
- assertTrue(workingDir.mkdir());
- createFile(new File(workingDir, "myFile"));
-
- ConfigBundle tzData = createValidTzDataBundle(NEW_RULES_VERSION);
- assertTrue(installer.install(tzData.getBundleBytes()));
- assertTzDataInstalled(tzData);
- }
-
- /**
- * Tests that a bundle without a bundle version file will be rejected.
- */
- public void testInstallWithMissingBundleVersionFile() throws Exception {
- File workingDir = installer.getWorkingDir();
- assertTrue(workingDir.mkdir());
-
- ConfigBundle tzData = createTzDataBundleWithoutFormatVersionFile(NEW_RULES_VERSION);
- assertFalse(installer.install(tzData.getBundleBytes()));
- assertNoContentInstalled();
- }
-
- private ConfigBundle createTzDataBundleWithoutFormatVersionFile(String tzDataVersion)
- throws IOException {
-
- // Create a valid bundle.
- ConfigBundle bundle = createValidTzDataBundle(tzDataVersion);
- byte[] bundleBytes = bundle.getBundleBytes();
-
- // Remove the version file to make the bundle invalid.
- ByteArrayOutputStream baos = new ByteArrayOutputStream(bundleBytes.length);
- try (ZipInputStream zipInputStream =
- new ZipInputStream(new ByteArrayInputStream(bundleBytes));
- ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
-
- ZipEntry entry;
- while ((entry = zipInputStream.getNextEntry()) != null) {
- if (!entry.getName().equals(ConfigBundle.BUNDLE_VERSION_FILE_NAME)) {
- zipOutputStream.putNextEntry(entry);
- Streams.copy(zipInputStream, zipOutputStream);
- zipOutputStream.closeEntry();
- }
- zipInputStream.closeEntry();
- }
- }
- return new ConfigBundle(baos.toByteArray());
- }
-
- /**
- * Tests that a bundle with an incorrect bundle version will be rejected.
- */
- public void testInstallWithInvalidBundleVersionFile() throws Exception {
- ConfigBundle tzData = createTzDataBundleWithInvalidBundleVersion(NEW_RULES_VERSION);
- assertFalse(installer.install(tzData.getBundleBytes()));
- assertNoContentInstalled();
- }
-
- public void testUninstall_noExistingDataBundle() throws Exception {
- assertFalse(installer.uninstall());
- assertNoContentInstalled();
- }
-
- public void testUninstall_existingDataBundle() throws Exception {
- File currentDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentDataDir.mkdir());
-
- assertTrue(installer.uninstall());
- assertNoContentInstalled();
- }
-
- public void testUninstall_oldDirsAlreadyExists() throws Exception {
- File oldTzDataDir = installer.getOldTzDataDir();
- assertTrue(oldTzDataDir.mkdir());
-
- File currentDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentDataDir.mkdir());
-
- assertTrue(installer.uninstall());
- assertNoContentInstalled();
- }
-
- private ConfigBundle createTzDataBundleWithInvalidBundleVersion(String tzDataVersion)
- throws IOException {
-
- // Create a valid bundle.
- ConfigBundle bundle = createValidTzDataBundle(tzDataVersion);
- byte[] bundleBytes = bundle.getBundleBytes();
-
- // Modify the bundle version file to be invalid.
- byte[] badVersionBytes = new byte[ConfigBundle.BUNDLE_VERSION_BYTES.length];
- System.arraycopy(ConfigBundle.BUNDLE_VERSION_BYTES, 0, badVersionBytes, 0,
- badVersionBytes.length);
- badVersionBytes[0]++;
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream(bundleBytes.length);
- try (ZipInputStream zipInputStream =
- new ZipInputStream(new ByteArrayInputStream(bundleBytes));
- ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
-
- ZipEntry entry;
- while ((entry = zipInputStream.getNextEntry()) != null) {
- zipOutputStream.putNextEntry(entry);
- if (entry.getName().equals(ConfigBundle.BUNDLE_VERSION_FILE_NAME)) {
- zipOutputStream.write(badVersionBytes);
- } else {
- Streams.copy(zipInputStream, zipOutputStream);
- }
- zipOutputStream.closeEntry();
- zipInputStream.closeEntry();
- }
- }
- return new ConfigBundle(baos.toByteArray());
- }
-
- private ConfigBundle createValidTzDataBundle(String tzDataVersion)
- throws IOException {
- return createValidTzDataBundleBuilder(tzDataVersion).build();
- }
-
- private TzDataBundleBuilder createValidTzDataBundleBuilder(String tzDataVersion)
- throws IOException {
-
- File bionicTzData = new File(tempDir, "zoneinfo");
- createTzDataFile(bionicTzData, tzDataVersion);
-
- File icuData = new File(tempDir, "icudata");
- createFile(icuData);
-
- return new TzDataBundleBuilder()
- .setTzDataVersion(tzDataVersion)
- .addBionicTzData(bionicTzData)
- .addIcuTzData(icuData);
- }
-
- private void assertTzDataInstalled(ConfigBundle expectedTzData) throws Exception {
- assertTrue(testInstallDir.exists());
-
- File currentTzDataDir = installer.getCurrentTzDataDir();
- assertTrue(currentTzDataDir.exists());
-
- File bundleVersionFile = new File(currentTzDataDir,
- ConfigBundle.BUNDLE_VERSION_FILE_NAME);
- assertTrue(bundleVersionFile.exists());
-
- File versionFile = new File(currentTzDataDir,
- ConfigBundle.TZ_DATA_VERSION_FILE_NAME);
- assertTrue(versionFile.exists());
-
- File bionicFile = new File(currentTzDataDir, ConfigBundle.ZONEINFO_FILE_NAME);
- assertTrue(bionicFile.exists());
-
- File icuFile = new File(currentTzDataDir, ConfigBundle.ICU_DATA_FILE_NAME);
- assertTrue(icuFile.exists());
-
- // Also check no working directory is left lying around.
- File workingDir = installer.getWorkingDir();
- assertFalse(workingDir.exists());
- }
-
- private void assertNoContentInstalled() {
- File currentTzDataDir = installer.getCurrentTzDataDir();
- assertFalse(currentTzDataDir.exists());
-
- // Also check no working directories are left lying around.
- File workingDir = installer.getWorkingDir();
- assertFalse(workingDir.exists());
-
- File oldDataDir = installer.getOldTzDataDir();
- assertFalse(oldDataDir.exists());
- }
-
- private void createTzDataFile(File file, String rulesVersion) {
- byte[] bytes = new ZoneInfoTestHelper.TzDataBuilder()
- .initializeToValid()
- .setHeaderMagic("tzdata" + rulesVersion)
- .build();
- try (FileOutputStream fos = new FileOutputStream(file)) {
- fos.write(bytes);
- } catch (IOException e) {
- fail(e.getMessage());
- }
-
- }
-
- private static void createFile(File file) {
- try (FileOutputStream fos = new FileOutputStream(file)) {
- fos.write('a');
- } catch (IOException e) {
- fail(e.getMessage());
- }
- }
-}