diff options
Diffstat (limited to 'luni')
201 files changed, 9526 insertions, 3516 deletions
diff --git a/luni/src/benchmark/native/libcore_io_Memory_bench.cpp b/luni/src/benchmark/native/libcore_io_Memory_bench.cpp new file mode 100644 index 0000000..0819c27 --- /dev/null +++ b/luni/src/benchmark/native/libcore_io_Memory_bench.cpp @@ -0,0 +1,105 @@ +/* + * 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. + */ + +// The functions we want to benchmark are static, so include the source code. +#include "luni/src/main/native/libcore_io_Memory.cpp" + +#include <benchmark/Benchmark.h> + +template<typename T, size_t ALIGN> +void swap_bench(testing::Benchmark* bench, void (*swap_func)(T*, const T*, size_t), + int iters, size_t num_elements) { + T* src; + T* dst; + T* src_elems; + T* dst_elems; + + if (ALIGN) { + src_elems = new T[num_elements + 1]; + dst_elems = new T[num_elements + 1]; + + src = reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(src_elems) + ALIGN); + dst = reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(dst_elems) + ALIGN); + } else { + src_elems = new T[num_elements]; + dst_elems = new T[num_elements]; + + src = src_elems; + dst = dst_elems; + } + + memset(dst, 0, sizeof(T) * num_elements); + memset(src, 0x12, sizeof(T) * num_elements); + + bench->StartBenchmarkTiming(); + + for (int i = 0; i < iters; i++) { + swap_func(src, dst, num_elements); + } + + bench->StopBenchmarkTiming(); + + delete[] src_elems; + delete[] dst_elems; +} + +#define AT_COMMON_VALUES \ + Arg(10)->Arg(100)->Arg(1000)->Arg(1024*10)->Arg(1024*100) + +BENCHMARK_WITH_ARG(BM_libcore_swapShorts_aligned, int)->AT_COMMON_VALUES; +void BM_libcore_swapShorts_aligned::Run(int iters, int num_shorts) { + swap_bench<jshort, 0>(this, swapShorts, iters, num_shorts); +} + +BENCHMARK_WITH_ARG(BM_libcore_swapInts_aligned, int)->AT_COMMON_VALUES; +void BM_libcore_swapInts_aligned::Run(int iters, int num_ints) { + swap_bench<jint, 0>(this, swapInts, iters, num_ints); +} + +BENCHMARK_WITH_ARG(BM_libcore_swapLongs_aligned, int)->AT_COMMON_VALUES; +void BM_libcore_swapLongs_aligned::Run(int iters, int num_longs) { + swap_bench<jlong, 0>(this, swapLongs, iters, num_longs); +} + +BENCHMARK_WITH_ARG(BM_libcore_swapShorts_unaligned1, int)->AT_COMMON_VALUES; +void BM_libcore_swapShorts_unaligned1::Run(int iters, int num_shorts) { + swap_bench<jshort, 1>(this, swapShorts, iters, num_shorts); +} + +BENCHMARK_WITH_ARG(BM_libcore_swapInts_unaligned1, int)->AT_COMMON_VALUES; +void BM_libcore_swapInts_unaligned1::Run(int iters, int num_ints) { + swap_bench<jint, 1>(this, swapInts, iters, num_ints); +} + +BENCHMARK_WITH_ARG(BM_libcore_swapLongs_unaligned1, int)->AT_COMMON_VALUES; +void BM_libcore_swapLongs_unaligned1::Run(int iters, int num_longs) { + swap_bench<jlong, 1>(this, swapLongs, iters, num_longs); +} + +BENCHMARK_WITH_ARG(BM_libcore_swapShorts_unaligned2, int)->AT_COMMON_VALUES; +void BM_libcore_swapShorts_unaligned2::Run(int iters, int num_shorts) { + swap_bench<jshort, 2>(this, swapShorts, iters, num_shorts); +} + +BENCHMARK_WITH_ARG(BM_libcore_swapInts_unaligned2, int)->AT_COMMON_VALUES; +void BM_libcore_swapInts_unaligned2::Run(int iters, int num_ints) { + swap_bench<jint, 2>(this, swapInts, iters, num_ints); +} + +BENCHMARK_WITH_ARG(BM_libcore_swapLongs_unaligned2, int)->AT_COMMON_VALUES; +void BM_libcore_swapLongs_unaligned2::Run(int iters, int num_longs) { + swap_bench<jlong, 2>(this, swapLongs, iters, num_longs); +} diff --git a/luni/src/main/files/README.cacerts b/luni/src/main/files/README.cacerts index e905a68..ca5c570 100755 --- a/luni/src/main/files/README.cacerts +++ b/luni/src/main/files/README.cacerts @@ -1,7 +1,7 @@ The filenames in the cacerts directory are in the format of <hash>.<n> where "hash" is the subject hash produced by: - openssl x509 -subject_hash -in filename + openssl x509 -subject_hash_old -in filename and the "n" is a unique integer identifier starting at 0 to deal with collisions. See OpenSSL's c_rehash manpage for details. diff --git a/luni/src/main/java/android/system/NetlinkSocketAddress.java b/luni/src/main/java/android/system/NetlinkSocketAddress.java new file mode 100644 index 0000000..af78cd0 --- /dev/null +++ b/luni/src/main/java/android/system/NetlinkSocketAddress.java @@ -0,0 +1,61 @@ +/* + * 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 android.system; + +import libcore.util.Objects; +import java.net.SocketAddress; + +/** + * Netlink socket address. + * + * Corresponds to Linux's {@code struct sockaddr_nl} from + * <a href="https://github.com/torvalds/linux/blob/master/include/uapi/linux/netlink.h"><linux/netlink.h></a>. + * + * @hide + */ +public final class NetlinkSocketAddress extends SocketAddress { + /** port ID */ + private final int nlPortId; + + /** multicast groups mask */ + private final int nlGroupsMask; + + public NetlinkSocketAddress() { + this(0, 0); + } + + public NetlinkSocketAddress(int nlPortId) { + this(nlPortId, 0); + } + + public NetlinkSocketAddress(int nlPortId, int nlGroupsMask) { + this.nlPortId = nlPortId; + this.nlGroupsMask = nlGroupsMask; + } + + public int getPortId() { + return nlPortId; + } + + public int getGroupsMask() { + return nlGroupsMask; + } + + @Override public String toString() { + return Objects.toString(this); + } +} diff --git a/luni/src/main/java/android/system/Os.java b/luni/src/main/java/android/system/Os.java index 9d6dc1b..fcecf18 100644 --- a/luni/src/main/java/android/system/Os.java +++ b/luni/src/main/java/android/system/Os.java @@ -54,6 +54,8 @@ public final class Os { */ public static void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.bind(fd, address, port); } + /** @hide */ public static void bind(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.bind(fd, address); } + /** * See <a href="http://man7.org/linux/man-pages/man2/chmod.2.html">chmod(2)</a>. */ @@ -74,6 +76,8 @@ public final class Os { */ public static void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.connect(fd, address, port); } + /** @hide */ public static void connect(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { Libcore.os.connect(fd, address); } + /** * See <a href="http://man7.org/linux/man-pages/man2/dup.2.html">dup(2)</a>. */ @@ -109,9 +113,9 @@ public final class Os { */ public static void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { Libcore.os.fchown(fd, uid, gid); } - /** @hide */ public static int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return Libcore.os.fcntlVoid(fd, cmd); } - /** @hide */ public static int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException { return Libcore.os.fcntlLong(fd, cmd, arg); } /** @hide */ public static int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException, InterruptedIOException { return Libcore.os.fcntlFlock(fd, cmd, arg); } + /** @hide */ public static int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException { return Libcore.os.fcntlInt(fd, cmd, arg); } + /** @hide */ public static int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return Libcore.os.fcntlVoid(fd, cmd); } /** * See <a href="http://man7.org/linux/man-pages/man2/fdatasync.2.html">fdatasync(2)</a>. @@ -171,6 +175,11 @@ public final class Os { public static SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return Libcore.os.getpeername(fd); } /** + * See <a href="http://man7.org/linux/man-pages/man2/getpgid.2.html">getpgid(2)</a>. + */ + /** @hide */ public static int getpgid(int pid) throws ErrnoException { return Libcore.os.getpgid(pid); } + + /** * See <a href="http://man7.org/linux/man-pages/man2/getpid.2.html">getpid(2)</a>. */ public static int getpid() { return Libcore.os.getpid(); } @@ -302,10 +311,16 @@ public final class Os { /** * See <a href="http://man7.org/linux/man-pages/man2/pipe.2.html">pipe(2)</a>. */ - public static FileDescriptor[] pipe() throws ErrnoException { return Libcore.os.pipe(); } + public static FileDescriptor[] pipe() throws ErrnoException { return Libcore.os.pipe2(0); } + + /** @hide */ public static FileDescriptor[] pipe2(int flags) throws ErrnoException { return Libcore.os.pipe2(flags); } /** * See <a href="http://man7.org/linux/man-pages/man2/poll.2.html">poll(2)</a>. + * + * <p>Note that in Lollipop this could throw an {@code ErrnoException} with {@code EINTR}. + * In later releases, the implementation will automatically just restart the system call with + * an appropriately reduced timeout. */ public static int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return Libcore.os.poll(fds, timeoutMs); } @@ -395,6 +410,11 @@ public final class Os { public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); } /** + * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>. + */ + /** @hide */ public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, address); } + + /** * See <a href="http://man7.org/linux/man-pages/man2/setegid.2.html">setegid(2)</a>. */ public static void setegid(int egid) throws ErrnoException { Libcore.os.setegid(egid); } @@ -415,6 +435,21 @@ public final class Os { public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); } /** + * See <a href="http://man7.org/linux/man-pages/man2/setpgid.2.html">setpgid(2)</a>. + */ + /** @hide */ public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/setregid.2.html">setregid(2)</a>. + */ + /** @hide */ public static void setregid(int rgid, int egid) throws ErrnoException { Libcore.os.setregid(rgid, egid); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/setreuid.2.html">setreuid(2)</a>. + */ + /** @hide */ public static void setreuid(int ruid, int euid) throws ErrnoException { Libcore.os.setreuid(ruid, euid); } + + /** * See <a href="http://man7.org/linux/man-pages/man2/setsid.2.html">setsid(2)</a>. */ public static int setsid() throws ErrnoException { return Libcore.os.setsid(); } diff --git a/luni/src/main/java/android/system/OsConstants.java b/luni/src/main/java/android/system/OsConstants.java index c758eb7..c0d31e5 100644 --- a/luni/src/main/java/android/system/OsConstants.java +++ b/luni/src/main/java/android/system/OsConstants.java @@ -95,6 +95,8 @@ public final class OsConstants { public static final int AF_INET = placeholder(); public static final int AF_INET6 = placeholder(); + /** @hide */ public static final int AF_NETLINK = placeholder(); + /** @hide */ public static final int AF_PACKET = placeholder(); public static final int AF_UNIX = placeholder(); public static final int AF_UNSPEC = placeholder(); public static final int AI_ADDRCONFIG = placeholder(); @@ -104,6 +106,8 @@ public final class OsConstants { public static final int AI_NUMERICSERV = placeholder(); public static final int AI_PASSIVE = placeholder(); public static final int AI_V4MAPPED = placeholder(); + /** @hide */ public static final int ARPHRD_ETHER = placeholder(); + /** @hide */ public static final int ARPHRD_LOOPBACK = placeholder(); public static final int CAP_AUDIT_CONTROL = placeholder(); public static final int CAP_AUDIT_WRITE = placeholder(); public static final int CAP_BLOCK_SUSPEND = placeholder(); @@ -227,6 +231,9 @@ public final class OsConstants { public static final int ESPIPE = placeholder(); public static final int ESRCH = placeholder(); public static final int ESTALE = placeholder(); + /** @hide */ public static final int ETH_P_ARP = placeholder(); + /** @hide */ public static final int ETH_P_IP = placeholder(); + /** @hide */ public static final int ETH_P_IPV6 = placeholder(); public static final int ETIME = placeholder(); public static final int ETIMEDOUT = placeholder(); public static final int ETXTBSY = placeholder(); @@ -324,6 +331,7 @@ public final class OsConstants { public static final int MS_ASYNC = placeholder(); public static final int MS_INVALIDATE = placeholder(); public static final int MS_SYNC = placeholder(); + /** @hide */ public static final int NETLINK_ROUTE = placeholder(); public static final int NI_DGRAM = placeholder(); public static final int NI_NAMEREQD = placeholder(); public static final int NI_NOFQDN = placeholder(); @@ -331,6 +339,7 @@ public final class OsConstants { public static final int NI_NUMERICSERV = placeholder(); public static final int O_ACCMODE = placeholder(); public static final int O_APPEND = placeholder(); + /** @hide */ public static final int O_CLOEXEC = placeholder(); public static final int O_CREAT = placeholder(); public static final int O_EXCL = placeholder(); public static final int O_NOCTTY = placeholder(); @@ -364,6 +373,19 @@ public final class OsConstants { public static final int RT_SCOPE_NOWHERE = placeholder(); public static final int RT_SCOPE_SITE = placeholder(); public static final int RT_SCOPE_UNIVERSE = placeholder(); + /** @hide */ public static final int RTMGRP_IPV4_IFADDR = placeholder(); + /** @hide */ public static final int RTMGRP_IPV4_MROUTE = placeholder(); + /** @hide */ public static final int RTMGRP_IPV4_ROUTE = placeholder(); + /** @hide */ public static final int RTMGRP_IPV4_RULE = placeholder(); + /** @hide */ public static final int RTMGRP_IPV6_IFADDR = placeholder(); + /** @hide */ public static final int RTMGRP_IPV6_IFINFO = placeholder(); + /** @hide */ public static final int RTMGRP_IPV6_MROUTE = placeholder(); + /** @hide */ public static final int RTMGRP_IPV6_PREFIX = placeholder(); + /** @hide */ public static final int RTMGRP_IPV6_ROUTE = placeholder(); + /** @hide */ public static final int RTMGRP_LINK = placeholder(); + /** @hide */ public static final int RTMGRP_NEIGH = placeholder(); + /** @hide */ public static final int RTMGRP_NOTIFY = placeholder(); + /** @hide */ public static final int RTMGRP_TC = placeholder(); public static final int SEEK_CUR = placeholder(); public static final int SEEK_END = placeholder(); public static final int SEEK_SET = placeholder(); @@ -433,6 +455,15 @@ public final class OsConstants { public static final int STDERR_FILENO = placeholder(); public static final int STDIN_FILENO = placeholder(); public static final int STDOUT_FILENO = placeholder(); + /** @hide */ public static final int ST_MANDLOCK = placeholder(); + /** @hide */ public static final int ST_NOATIME = placeholder(); + /** @hide */ public static final int ST_NODEV = placeholder(); + /** @hide */ public static final int ST_NODIRATIME = placeholder(); + /** @hide */ public static final int ST_NOEXEC = placeholder(); + /** @hide */ public static final int ST_NOSUID = placeholder(); + /** @hide */ public static final int ST_RDONLY = placeholder(); + /** @hide */ public static final int ST_RELATIME = placeholder(); + /** @hide */ public static final int ST_SYNCHRONOUS = placeholder(); public static final int S_IFBLK = placeholder(); public static final int S_IFCHR = placeholder(); public static final int S_IFDIR = placeholder(); diff --git a/luni/src/main/java/android/system/PacketSocketAddress.java b/luni/src/main/java/android/system/PacketSocketAddress.java new file mode 100644 index 0000000..510771c --- /dev/null +++ b/luni/src/main/java/android/system/PacketSocketAddress.java @@ -0,0 +1,64 @@ +/* + * 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 android.system; + +import libcore.util.Objects; +import java.net.SocketAddress; + +/** + * Packet socket address. + * + * Corresponds to Linux's {@code struct sockaddr_ll}. + * + * @hide + */ +public final class PacketSocketAddress extends SocketAddress { + /** Protocol. An Ethernet protocol type, e.g., {@code ETH_P_IPV6}. */ + public short sll_protocol; + + /** Interface index. */ + public int sll_ifindex; + + /** ARP hardware type. One of the {@code ARPHRD_*} constants. */ + public short sll_hatype; + + /** Packet type. One of the {@code PACKET_*} constants, such as {@code PACKET_OTHERHOST}. */ + public byte sll_pkttype; + + /** Hardware address. */ + public byte[] sll_addr; + + /** Constructs a new PacketSocketAddress. */ + public PacketSocketAddress(short sll_protocol, int sll_ifindex, + short sll_hatype, byte sll_pkttype, byte[] sll_addr) { + this.sll_protocol = sll_protocol; + this.sll_ifindex = sll_ifindex; + this.sll_hatype = sll_hatype; + this.sll_pkttype = sll_pkttype; + this.sll_addr = sll_addr; + } + + /** Constructs a new PacketSocketAddress suitable for binding to. */ + public PacketSocketAddress(short sll_protocol, int sll_ifindex) { + this(sll_protocol, sll_ifindex, (short) 0, (byte) 0, null); + } + + /** Constructs a new PacketSocketAddress suitable for sending to. */ + public PacketSocketAddress(int sll_ifindex, byte[] sll_addr) { + this((short) 0, sll_ifindex, (short) 0, (byte) 0, sll_addr); + } +} diff --git a/luni/src/main/java/java/io/BufferedInputStream.java b/luni/src/main/java/java/io/BufferedInputStream.java index 85236b6..ec43720 100644 --- a/luni/src/main/java/java/io/BufferedInputStream.java +++ b/luni/src/main/java/java/io/BufferedInputStream.java @@ -150,7 +150,7 @@ public class BufferedInputStream extends FilterInputStream { if (result > 0) { markpos = -1; pos = 0; - count = result == -1 ? 0 : result; + count = result; } return result; } diff --git a/luni/src/main/java/java/io/File.java b/luni/src/main/java/java/io/File.java index d107c28..0592345 100644 --- a/luni/src/main/java/java/io/File.java +++ b/luni/src/main/java/java/io/File.java @@ -57,12 +57,6 @@ public class File implements Serializable, Comparable<File> { private static final long serialVersionUID = 301077366599181567L; /** - * Reusing a Random makes temporary filenames slightly harder to predict. - * (Random is thread-safe.) - */ - private static final Random tempFileRandom = new Random(); - - /** * The system-dependent character used to separate components in filenames ('/'). * Use of this (rather than hard-coding '/') helps portability to other operating systems. * @@ -129,6 +123,8 @@ public class File implements Serializable, Comparable<File> { * * @param path * the path to be used for the file. + * @throws NullPointerException + * if {@code path} is {@code null}. */ public File(String path) { this.path = fixSlashes(path); @@ -167,6 +163,8 @@ public class File implements Serializable, Comparable<File> { * @param uri * the Unified Resource Identifier that is used to construct this * file. + * @throws NullPointerException + * if {@code uri == null}. * @throws IllegalArgumentException * if {@code uri} does not comply with the conditions above. * @see #toURI @@ -1002,7 +1000,7 @@ public class File implements Serializable, Comparable<File> { } File result; do { - result = new File(tmpDirFile, prefix + tempFileRandom.nextInt() + suffix); + result = new File(tmpDirFile, prefix + Math.randomIntInternal() + suffix); } while (!result.createNewFile()); return result; } diff --git a/luni/src/main/java/java/io/FileDescriptor.java b/luni/src/main/java/java/io/FileDescriptor.java index eba0e4d..be94c52 100644 --- a/luni/src/main/java/java/io/FileDescriptor.java +++ b/luni/src/main/java/java/io/FileDescriptor.java @@ -108,7 +108,7 @@ public final class FileDescriptor { /** * @hide internal use only */ - public boolean isSocket() { + public final boolean isSocket$() { return isSocket(descriptor); } diff --git a/luni/src/main/java/java/io/InputStreamReader.java b/luni/src/main/java/java/io/InputStreamReader.java index d57b916..a4cacf2 100644 --- a/luni/src/main/java/java/io/InputStreamReader.java +++ b/luni/src/main/java/java/io/InputStreamReader.java @@ -101,7 +101,9 @@ public class InputStreamReader extends Reader { */ public InputStreamReader(InputStream in, CharsetDecoder dec) { super(in); - dec.averageCharsPerByte(); + if (dec == null) { + throw new NullPointerException("dec == null"); + } this.in = in; decoder = dec; bytes.limit(0); diff --git a/luni/src/main/java/java/io/ObjectInputStream.java b/luni/src/main/java/java/io/ObjectInputStream.java index 3a89b52..cd267b2 100644 --- a/luni/src/main/java/java/io/ObjectInputStream.java +++ b/luni/src/main/java/java/io/ObjectInputStream.java @@ -1977,7 +1977,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec // original/outside caller if (++nestedLevels == 1) { // Remember the caller's class loader - callerClassLoader = VMStack.getClosestUserClassLoader(bootstrapLoader, systemLoader); + callerClassLoader = VMStack.getClosestUserClassLoader(); } result = readNonPrimitiveContent(unshared); @@ -2014,9 +2014,6 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec return result; } - private static final ClassLoader bootstrapLoader = Object.class.getClassLoader(); - private static final ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); - /** * Method to be overridden by subclasses to read the next object from the * source stream. @@ -2258,8 +2255,6 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec if (cls == null) { // not primitive class - // Use the first non-null ClassLoader on the stack. If null, use - // the system class loader cls = Class.forName(className, false, callerClassLoader); } } diff --git a/luni/src/main/java/java/io/ObjectStreamField.java b/luni/src/main/java/java/io/ObjectStreamField.java index 78a6903..2a9b107 100644 --- a/luni/src/main/java/java/io/ObjectStreamField.java +++ b/luni/src/main/java/java/io/ObjectStreamField.java @@ -58,13 +58,7 @@ public class ObjectStreamField implements Comparable<Object> { * if {@code name} or {@code cl} is {@code null}. */ public ObjectStreamField(String name, Class<?> cl) { - if (name == null) { - throw new NullPointerException("name == null"); - } else if (cl == null) { - throw new NullPointerException("cl == null"); - } - this.name = name; - this.type = new WeakReference<Class<?>>(cl); + this(name, cl, false); } /** diff --git a/luni/src/main/java/java/io/OutputStreamWriter.java b/luni/src/main/java/java/io/OutputStreamWriter.java index bc8710d..8a639e7 100644 --- a/luni/src/main/java/java/io/OutputStreamWriter.java +++ b/luni/src/main/java/java/io/OutputStreamWriter.java @@ -116,7 +116,10 @@ public class OutputStreamWriter extends Writer { */ public OutputStreamWriter(OutputStream out, CharsetEncoder charsetEncoder) { super(out); - charsetEncoder.charset(); + if (charsetEncoder == null) { + throw new NullPointerException("charsetEncoder == null"); + } + this.out = out; encoder = charsetEncoder; } diff --git a/luni/src/main/java/java/io/RandomAccessFile.java b/luni/src/main/java/java/io/RandomAccessFile.java index da99765..0e4fa4f 100644 --- a/luni/src/main/java/java/io/RandomAccessFile.java +++ b/luni/src/main/java/java/io/RandomAccessFile.java @@ -160,7 +160,6 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable { synchronized (this) { if (channel != null && channel.isOpen()) { channel.close(); - channel = null; } IoBridge.closeAndSignalBlockedThreads(fd); } @@ -185,6 +184,10 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable { * changes made to this file's file pointer offset are also visible in the * file channel's position and vice versa. * + * Closing the channel closes the RandomAccessFile as well. The instance + * of FileChannel returned is always the same even if the RandomAccessFile + * or the FileChannel have been closed. + * * @return this file's file channel instance. */ public final synchronized FileChannel getChannel() { diff --git a/luni/src/main/java/java/lang/Math.java b/luni/src/main/java/java/lang/Math.java index 86df784..7203566 100644 --- a/luni/src/main/java/java/lang/Math.java +++ b/luni/src/main/java/java/lang/Math.java @@ -35,7 +35,9 @@ public final class Math { */ public static final double PI = 3.141592653589793; - private static Random random; + private static class NoImagePreloadHolder { + private static final Random INSTANCE = new Random(); + } /** * Prevents this class from being instantiated. @@ -875,11 +877,25 @@ public final class Math { * * @return a pseudo-random number. */ - public static synchronized double random() { - if (random == null) { - random = new Random(); - } - return random.nextDouble(); + public static double random() { + return NoImagePreloadHolder.INSTANCE.nextDouble(); + } + + /** + * Set the seed for the pseudo random generator used by {@link #random()} + * and {@link #randomIntInternal()}. + * + * @hide for internal use only. + */ + public static void setRandomSeedInternal(long seed) { + NoImagePreloadHolder.INSTANCE.setSeed(seed); + } + + /** + * @hide for internal use only. + */ + public static int randomIntInternal() { + return NoImagePreloadHolder.INSTANCE.nextInt(); } /** diff --git a/luni/src/main/java/java/lang/Package.java b/luni/src/main/java/java/lang/Package.java index 7e30883..3c6c39c 100644 --- a/luni/src/main/java/java/lang/Package.java +++ b/luni/src/main/java/java/lang/Package.java @@ -51,6 +51,7 @@ import java.net.URL; public class Package implements AnnotatedElement { private static final Annotation[] NO_ANNOTATIONS = new Annotation[0]; + private final ClassLoader classLoader; private final String name; private final String specTitle; private final String specVersion; @@ -60,8 +61,10 @@ public class Package implements AnnotatedElement { private final String implVendor; private final URL sealBase; - Package(String name, String specTitle, String specVersion, String specVendor, - String implTitle, String implVersion, String implVendor, URL sealBase) { + Package(ClassLoader classLoader, String name, String specTitle, String specVersion, + String specVendor, String implTitle, String implVersion, String implVendor, + URL sealBase) { + this.classLoader = classLoader; this.name = name; this.specTitle = specTitle; this.specVersion = specVersion; @@ -96,7 +99,8 @@ public class Package implements AnnotatedElement { */ public Annotation[] getAnnotations() { try { - Class<?> c = Class.forName(getName() + ".package-info"); + Class<?> c = Class.forName(getName() + ".package-info", false /* initialize */, + classLoader); return c.getAnnotations(); } catch (Exception ex) { return NO_ANNOTATIONS; @@ -175,11 +179,11 @@ public class Package implements AnnotatedElement { * @see ClassLoader#getPackage(java.lang.String) */ public static Package getPackage(String packageName) { - ClassLoader classloader = VMStack.getCallingClassLoader(); - if (classloader == null) { - classloader = ClassLoader.getSystemClassLoader(); + ClassLoader classLoader = VMStack.getCallingClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); } - return classloader.getPackage(packageName); + return classLoader.getPackage(packageName); } /** @@ -189,11 +193,11 @@ public class Package implements AnnotatedElement { * @see ClassLoader#getPackages */ public static Package[] getPackages() { - ClassLoader classloader = VMStack.getCallingClassLoader(); - if (classloader == null) { - classloader = ClassLoader.getSystemClassLoader(); + ClassLoader classLoader = VMStack.getCallingClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); } - return classloader.getPackages(); + return classLoader.getPackages(); } /** diff --git a/luni/src/main/java/java/lang/Runtime.java b/luni/src/main/java/java/lang/Runtime.java index a3cb83e..3ddacf7 100644 --- a/luni/src/main/java/java/lang/Runtime.java +++ b/luni/src/main/java/java/lang/Runtime.java @@ -357,14 +357,11 @@ public class Runtime { */ void loadLibrary(String libraryName, ClassLoader loader) { if (loader != null) { + // TODO: We shouldn't assume that we know default linker search logic. String filename = loader.findLibrary(libraryName); if (filename == null) { - // It's not necessarily true that the ClassLoader used - // System.mapLibraryName, but the default setup does, and it's - // misleading to say we didn't find "libMyLibrary.so" when we - // actually searched for "liblibMyLibrary.so.so". - throw new UnsatisfiedLinkError(loader + " couldn't find \"" + - System.mapLibraryName(libraryName) + "\""); + // The dynamic linker might still find the library by name. + filename = System.mapLibraryName(libraryName); } String error = doLoad(filename, loader); if (error != null) { @@ -418,19 +415,27 @@ public class Runtime { // So, find out what the native library search path is for the ClassLoader in question... String ldLibraryPath = null; - if (loader != null && loader instanceof BaseDexClassLoader) { - ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath(); + String dexPath = null; + if (loader == null) { + // We use the given library path for the boot class loader. This is the path + // also used in loadLibraryName if loader is null. + ldLibraryPath = System.getProperty("java.library.path"); + } else if (loader instanceof BaseDexClassLoader) { + BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader; + ldLibraryPath = dexClassLoader.getLdLibraryPath(); + dexPath = dexClassLoader.getDexPath(); } // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized // internal natives. synchronized (this) { - return nativeLoad(name, loader, ldLibraryPath); + return nativeLoad(name, loader, ldLibraryPath, dexPath); } } // TODO: should be synchronized, but dalvik doesn't support synchronized internal natives. - private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath); + private static native String nativeLoad(String filename, ClassLoader loader, + String ldLibraryPath, String dexPath); /** * Provides a hint to the VM that it would be useful to attempt diff --git a/luni/src/main/java/java/lang/StrictMath.java b/luni/src/main/java/java/lang/StrictMath.java index f409c06..2e848f2 100644 --- a/luni/src/main/java/java/lang/StrictMath.java +++ b/luni/src/main/java/java/lang/StrictMath.java @@ -15,6 +15,18 @@ * limitations under the License. */ +/* + * acos, asin, atan, cosh, sinh, tanh, exp, expm1, log, log10, log1p, and cbrt + * have been implemented with the following license. + * ==================================================== + * Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + package java.lang; /** @@ -102,6 +114,21 @@ public final class StrictMath { return Math.abs(l); } + private static final double PIO2_HI = 1.57079632679489655800e+00; + private static final double PIO2_LO = 6.12323399573676603587e-17; + private static final double PS0 = 1.66666666666666657415e-01; + private static final double PS1 = -3.25565818622400915405e-01; + private static final double PS2 = 2.01212532134862925881e-01; + private static final double PS3 = -4.00555345006794114027e-02; + private static final double PS4 = 7.91534994289814532176e-04; + private static final double PS5 = 3.47933107596021167570e-05; + private static final double QS1 = -2.40339491173441421878e+00; + private static final double QS2 = 2.02094576023350569471e+00; + private static final double QS3 = -6.88283971605453293030e-01; + private static final double QS4 = 7.70381505559019352791e-02; + private static final double HUGE = 1.000e+300; + private static final double PIO4_HI = 7.85398163397448278999e-01; + /** * Returns the closest double approximation of the arc cosine of the * argument within the range {@code [0..pi]}. @@ -113,11 +140,62 @@ public final class StrictMath { * <li>{@code acos(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value to compute arc cosine of. * @return the arc cosine of the argument. */ - public static native double acos(double d); + public static double acos(double x) { + double z, p, q, r, w, s, c, df; + int hx, ix; + final long bits = Double.doubleToRawLongBits(x); + hx = (int) (bits >>> 32); + ix = hx & 0x7fffffff; + if (ix >= 0x3ff00000) { /* |x| >= 1 */ + if ((((ix - 0x3ff00000) | ((int) bits))) == 0) { /* |x|==1 */ + if (hx > 0) { + return 0.0; /* ieee_acos(1) = 0 */ + } else { + return 3.14159265358979311600e+00 + 2.0 * PIO2_LO; /* ieee_acos(-1)= pi */ + } + } + return (x - x) / (x - x); /* ieee_acos(|x|>1) is NaN */ + } + + if (ix < 0x3fe00000) { /* |x| < 0.5 */ + if (ix <= 0x3c600000) { + return PIO2_HI + PIO2_LO;/* if|x|<2**-57 */ + } + + z = x * x; + p = z * (PS0 + z + * (PS1 + z * (PS2 + z * (PS3 + z * (PS4 + z * PS5))))); + q = 1.00000000000000000000e+00 + z * (QS1 + z * (QS2 + z * (QS3 + z * QS4))); + r = p / q; + return PIO2_HI - (x - (PIO2_LO - x * r)); + } else if (hx < 0) { /* x < -0.5 */ + z = (1.00000000000000000000e+00 + x) * 0.5; + p = z * (PS0 + z + * (PS1 + z * (PS2 + z * (PS3 + z * (PS4 + z * PS5))))); + q = 1.00000000000000000000e+00 + z * (QS1 + z * (QS2 + z * (QS3 + z * QS4))); + s = StrictMath.sqrt(z); + r = p / q; + w = r * s - PIO2_LO; + return 3.14159265358979311600e+00 - 2.0 * (s + w); + } else { /* x > 0.5 */ + z = (1.00000000000000000000e+00 - x) * 0.5; + s = StrictMath.sqrt(z); + df = s; + df = Double.longBitsToDouble( + Double.doubleToRawLongBits(df) & 0xffffffffL << 32); + c = (z - df * df) / (s + df); + p = z * (PS0 + z + * (PS1 + z * (PS2 + z * (PS3 + z * (PS4 + z * PS5))))); + q = 1.00000000000000000000e+00 + z * (QS1 + z * (QS2 + z * (QS3 + z * QS4))); + r = p / q; + w = r * s + c; + return 2.0 * (df + w); + } + } /** * Returns the closest double approximation of the arc sine of the argument @@ -130,11 +208,75 @@ public final class StrictMath { * <li>{@code asin(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose arc sine has to be computed. * @return the arc sine of the argument. */ - public static native double asin(double d); + public static double asin(double x) { + double t, w, p, q, c, r, s; + int hx, ix; + final long bits = Double.doubleToRawLongBits(x); + hx = (int) (bits >>> 32); + ix = hx & 0x7fffffff; + if (ix >= 0x3ff00000) { /* |x|>= 1 */ + if ((((ix - 0x3ff00000) | ((int) bits))) == 0) { + /* ieee_asin(1)=+-pi/2 with inexact */ + return x * PIO2_HI + x * PIO2_LO; + } + return (x - x) / (x - x); /* ieee_asin(|x|>1) is NaN */ + } else if (ix < 0x3fe00000) { /* |x|<0.5 */ + if (ix < 0x3e400000) { /* if |x| < 2**-27 */ + if (HUGE + x > 1.00000000000000000000e+00) { + return x;/* return x with inexact if x!=0 */ + } + } else { + t = x * x; + p = t * (PS0 + t + * (PS1 + t * (PS2 + t * (PS3 + t * (PS4 + t * PS5))))); + q = 1.00000000000000000000e+00 + t * (QS1 + t * (QS2 + t * (QS3 + t * QS4))); + w = p / q; + return x + x * w; + } + } + /* 1> |x|>= 0.5 */ + w = 1.00000000000000000000e+00 - Math.abs(x); + t = w * 0.5; + p = t * (PS0 + t * (PS1 + t * (PS2 + t * (PS3 + t * (PS4 + t * PS5))))); + q = 1.00000000000000000000e+00 + t * (QS1 + t * (QS2 + t * (QS3 + t * QS4))); + s = StrictMath.sqrt(t); + if (ix >= 0x3FEF3333) { /* if |x| > 0.975 */ + w = p / q; + t = PIO2_HI - (2.0 * (s + s * w) - PIO2_LO); + } else { + w = s; + w = Double.longBitsToDouble( + Double.doubleToRawLongBits(w) & 0xffffffffL << 32); + c = (t - w * w) / (s + w); + r = p / q; + p = 2.0 * s * r - (PIO2_LO - 2.0 * c); + q = PIO4_HI - 2.0 * w; + t = PIO4_HI - (p - q); + } + return (hx > 0) ? t : -t; + } + + private static final double[] ATANHI = { 4.63647609000806093515e-01, + 7.85398163397448278999e-01, 9.82793723247329054082e-01, + 1.57079632679489655800e+00 }; + private static final double[] ATANLO = { 2.26987774529616870924e-17, + 3.06161699786838301793e-17, 1.39033110312309984516e-17, + 6.12323399573676603587e-17 }; + private static final double AT0 = 3.33333333333329318027e-01; + private static final double AT1 = -1.99999999998764832476e-01; + private static final double AT2 = 1.42857142725034663711e-01; + private static final double AT3 = -1.11111104054623557880e-01; + private static final double AT4 = 9.09088713343650656196e-02; + private static final double AT5 = -7.69187620504482999495e-02; + private static final double AT6 = 6.66107313738753120669e-02; + private static final double AT7= -5.83357013379057348645e-02; + private static final double AT8 = 4.97687799461593236017e-02; + private static final double AT9 = -3.65315727442169155270e-02; + private static final double AT10 = 1.62858201153657823623e-02; /** * Returns the closest double approximation of the arc tangent of the @@ -149,11 +291,73 @@ public final class StrictMath { * <li>{@code atan(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose arc tangent has to be computed. * @return the arc tangent of the argument. */ - public static native double atan(double d); + public static double atan(double x) { + double w, s1, s2, z; + int ix, hx, id; + + final long bits = Double.doubleToRawLongBits(x); + hx = (int) (bits >>> 32); + ix = hx & 0x7fffffff; + if (ix >= 0x44100000) { /* if |x| >= 2^66 */ + if (ix > 0x7ff00000 || (ix == 0x7ff00000 && (((int) bits) != 0))) { + return x + x; /* NaN */ + } + if (hx > 0) { + return ATANHI[3] + ATANLO[3]; + } else { + return -ATANHI[3] - ATANLO[3]; + } + } + if (ix < 0x3fdc0000) { /* |x| < 0.4375 */ + if (ix < 0x3e200000) { /* |x| < 2^-29 */ + if (HUGE + x > 1.00000000000000000000e+00) { + return x; /* raise inexact */ + } + } + id = -1; + } else { + x = Math.abs(x); + if (ix < 0x3ff30000) { /* |x| < 1.1875 */ + if (ix < 0x3fe60000) { /* 7/16 <=|x|<11/16 */ + id = 0; + x = (2.0 * x - 1.00000000000000000000e+00) / (2.0 + x); + } else { /* 11/16<=|x|< 19/16 */ + id = 1; + x = (x - 1.00000000000000000000e+00) / (x + 1.00000000000000000000e+00); + } + } else { + if (ix < 0x40038000) { /* |x| < 2.4375 */ + id = 2; + x = (x - 1.5) / (1.00000000000000000000e+00 + 1.5 * x); + } else { /* 2.4375 <= |x| < 2^66 */ + id = 3; + x = -1.0 / x; + } + } + } + + /* end of argument reduction */ + z = x * x; + w = z * z; + /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */ + s1 = z * (AT0 + w * (AT2 + w + * (AT4 + w * (AT6 + w * (AT8 + w * AT10))))); + s2 = w * (AT1 + w * (AT3 + w * (AT5 + w * (AT7 + w * AT9)))); + if (id < 0) { + return x - x * (s1 + s2); + } else { + z = ATANHI[id] - ((x * (s1 + s2) - ATANLO[id]) - x); + return (hx < 0) ? -z : z; + } + } + + private static final double PI_O_4 = 7.8539816339744827900E-01; + private static final double PI_O_2 = 1.5707963267948965580E+00; + private static final double PI_LO = 1.2246467991473531772E-16; /** * Returns the closest double approximation of the arc tangent of @@ -192,7 +396,108 @@ public final class StrictMath { * the denominator of the value whose atan has to be computed. * @return the arc tangent of {@code y/x}. */ - public static native double atan2(double y, double x); + public static double atan2(double y, double x) { + double z; + int k, m, hx, hy, ix, iy; + int lx, ly; // watch out, should be unsigned + + final long yBits = Double.doubleToRawLongBits(y); + final long xBits = Double.doubleToRawLongBits(x); + + hx = (int) (xBits >>> 32); // __HI(x); + ix = hx & 0x7fffffff; + lx = (int) xBits; // __LO(x); + hy = (int) (yBits >>> 32); // __HI(y); + iy = hy & 0x7fffffff; + ly = (int) yBits; // __LO(y); + if (((ix | ((lx | -lx) >> 31)) > 0x7ff00000) + || ((iy | ((ly | -ly) >> 31)) > 0x7ff00000)) { /* x or y is NaN */ + return x + y; + } + if ((hx - 0x3ff00000 | lx) == 0) { + return StrictMath.atan(y); /* x=1.0 */ + } + + m = ((hy >> 31) & 1) | ((hx >> 30) & 2); /* 2*sign(x)+sign(y) */ + + /* when y = 0 */ + if ((iy | ly) == 0) { + switch (m) { + case 0: + case 1: + return y; /* ieee_atan(+-0,+anything)=+-0 */ + case 2: + return 3.14159265358979311600e+00 + TINY;/* ieee_atan(+0,-anything) = pi */ + case 3: + return -3.14159265358979311600e+00 - TINY;/* ieee_atan(-0,-anything) =-pi */ + } + } + /* when x = 0 */ + if ((ix | lx) == 0) + return (hy < 0) ? -PI_O_2 - TINY : PI_O_2 + TINY; + + /* when x is INF */ + if (ix == 0x7ff00000) { + if (iy == 0x7ff00000) { + switch (m) { + case 0: + return PI_O_4 + TINY;/* ieee_atan(+INF,+INF) */ + case 1: + return -PI_O_4 - TINY;/* ieee_atan(-INF,+INF) */ + case 2: + return 3.0 * PI_O_4 + TINY;/* ieee_atan(+INF,-INF) */ + case 3: + return -3.0 * PI_O_4 - TINY;/* ieee_atan(-INF,-INF) */ + } + } else { + switch (m) { + case 0: + return 0.0; /* ieee_atan(+...,+INF) */ + case 1: + return -0.0; /* ieee_atan(-...,+INF) */ + case 2: + return 3.14159265358979311600e+00 + TINY; /* ieee_atan(+...,-INF) */ + case 3: + return -3.14159265358979311600e+00 - TINY; /* ieee_atan(-...,-INF) */ + } + } + } + /* when y is INF */ + if (iy == 0x7ff00000) + return (hy < 0) ? -PI_O_2 - TINY : PI_O_2 + TINY; + + /* compute y/x */ + k = (iy - ix) >> 20; + if (k > 60) { + z = PI_O_2 + 0.5 * PI_LO; /* |y/x| > 2**60 */ + } else if (hx < 0 && k < -60) { + z = 0.0; /* |y|/x < -2**60 */ + } else { + z = StrictMath.atan(Math.abs(y / x)); /* safe to do y/x */ + } + + switch (m) { + case 0: + return z; /* ieee_atan(+,+) */ + case 1: + // __HI(z) ^= 0x80000000; + z = Double.longBitsToDouble( + Double.doubleToRawLongBits(z) ^ (0x80000000L << 32)); + return z; /* ieee_atan(-,+) */ + case 2: + return 3.14159265358979311600e+00 - (z - PI_LO);/* ieee_atan(+,-) */ + default: /* case 3 */ + return (z - PI_LO) - 3.14159265358979311600e+00;/* ieee_atan(-,-) */ + } + } + + private static final int B1 = 715094163; + private static final int B2 = 696219795; + private static final double C = 5.42857142857142815906e-01; + private static final double D = -7.05306122448979611050e-01; + private static final double CBRTE = 1.41428571428571436819e+00; + private static final double F = 1.60714285714285720630e+00; + private static final double G = 3.57142857142857150787e-01; /** * Returns the closest double approximation of the cube root of the @@ -207,11 +512,79 @@ public final class StrictMath { * <li>{@code cbrt(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose cube root has to be computed. * @return the cube root of the argument. */ - public static native double cbrt(double d); + public static double cbrt(double x) { + if (x < 0) { + return -cbrt(-x); + } + int hx; + double r, s, w; + int sign; // caution: should be unsigned + long bits = Double.doubleToRawLongBits(x); + + hx = (int) (bits >>> 32); + sign = hx & 0x80000000; /* sign= sign(x) */ + hx ^= sign; + if (hx >= 0x7ff00000) { + return (x + x); /* ieee_cbrt(NaN,INF) is itself */ + } + + if ((hx | ((int) bits)) == 0) { + return x; /* ieee_cbrt(0) is itself */ + } + + // __HI(x) = hx; /* x <- |x| */ + bits &= 0x00000000ffffffffL; + bits |= ((long) hx << 32); + + long tBits = Double.doubleToRawLongBits(0.0) & 0x00000000ffffffffL; + double t = 0.0; + /* rough cbrt to 5 bits */ + if (hx < 0x00100000) { /* subnormal number */ + // __HI(t)=0x43500000; /*set t= 2**54*/ + tBits |= 0x43500000L << 32; + t = Double.longBitsToDouble(tBits); + t *= x; + + // __HI(t)=__HI(t)/3+B2; + tBits = Double.doubleToRawLongBits(t); + long tBitsHigh = tBits >> 32; + tBits &= 0x00000000ffffffffL; + tBits |= ((tBitsHigh / 3) + B2) << 32; + t = Double.longBitsToDouble(tBits); + + } else { + // __HI(t)=hx/3+B1; + tBits |= ((long) ((hx / 3) + B1)) << 32; + t = Double.longBitsToDouble(tBits); + } + + /* new cbrt to 23 bits, may be implemented in single precision */ + r = t * t / x; + s = C + r * t; + t *= G + F / (s + CBRTE + D / s); + + /* chopped to 20 bits and make it larger than ieee_cbrt(x) */ + tBits = Double.doubleToRawLongBits(t); + tBits &= 0xFFFFFFFFL << 32; + tBits += 0x00000001L << 32; + t = Double.longBitsToDouble(tBits); + + /* one step newton iteration to 53 bits with error less than 0.667 ulps */ + s = t * t; /* t*t is exact */ + r = x / s; + w = t + t; + r = (r - t) / (w + r); /* r-s is exact */ + t = t + t * r; + + /* retore the sign bit */ + tBits = Double.doubleToRawLongBits(t); + tBits |= ((long) sign) << 32; + return Double.longBitsToDouble(tBits); + } /** * Returns the double conversion of the most negative (closest to negative @@ -229,6 +602,8 @@ public final class StrictMath { */ public static native double ceil(double d); + private static final long ONEBITS = Double.doubleToRawLongBits(1.00000000000000000000e+00) + & 0x00000000ffffffffL; /** * Returns the closest double approximation of the hyperbolic cosine of the @@ -241,11 +616,54 @@ public final class StrictMath { * <li>{@code cosh(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose hyperbolic cosine has to be computed. * @return the hyperbolic cosine of the argument. */ - public static native double cosh(double d); + public static double cosh(double x) { + double t, w; + int ix; + final long bits = Double.doubleToRawLongBits(x); + ix = (int) (bits >>> 32) & 0x7fffffff; + + /* x is INF or NaN */ + if (ix >= 0x7ff00000) { + return x * x; + } + + /* |x| in [0,0.5*ln2], return 1+ieee_expm1(|x|)^2/(2*ieee_exp(|x|)) */ + if (ix < 0x3fd62e43) { + t = expm1(Math.abs(x)); + w = 1.00000000000000000000e+00 + t; + if (ix < 0x3c800000) + return w; /* ieee_cosh(tiny) = 1 */ + return 1.00000000000000000000e+00 + (t * t) / (w + w); + } + + /* |x| in [0.5*ln2,22], return (ieee_exp(|x|)+1/ieee_exp(|x|)/2; */ + if (ix < 0x40360000) { + t = exp(Math.abs(x)); + return 0.5 * t + 0.5 / t; + } + + /* |x| in [22, ieee_log(maxdouble)] return half*ieee_exp(|x|) */ + if (ix < 0x40862E42) { + return 0.5 * exp(Math.abs(x)); + } + + /* |x| in [log(maxdouble), overflowthresold] */ + final long lx = ((ONEBITS >>> 29) + ((int) bits)) & 0x00000000ffffffffL; + // watch out: lx should be an unsigned int + // lx = *( (((*(unsigned*)&one)>>29)) + (unsigned*)&x); + if (ix < 0x408633CE || (ix == 0x408633ce) && (lx <= 0x8fb9f87dL)) { + w = exp(0.5 * Math.abs(x)); + t = 0.5 * w; + return t * w; + } + + /* |x| > overflowthresold, ieee_cosh(x) overflow */ + return HUGE * HUGE; + } /** * Returns the closest double approximation of the cosine of the argument. @@ -263,6 +681,19 @@ public final class StrictMath { */ public static native double cos(double d); + private static final double TWON24 = 5.96046447753906250000e-08; + private static final double TWO54 = 1.80143985094819840000e+16, + TWOM54 = 5.55111512312578270212e-17; + private static final double TWOM1000 = 9.33263618503218878990e-302; + private static final double O_THRESHOLD = 7.09782712893383973096e+02; + private static final double U_THRESHOLD = -7.45133219101941108420e+02; + private static final double INVLN2 = 1.44269504088896338700e+00; + private static final double P1 = 1.66666666666666019037e-01; + private static final double P2 = -2.77777777770155933842e-03; + private static final double P3 = 6.61375632143793436117e-05; + private static final double P4 = -1.65339022054652515390e-06; + private static final double P5 = 4.13813679705723846039e-08; + /** * Returns the closest double approximation of the raising "e" to the power * of the argument. @@ -274,11 +705,88 @@ public final class StrictMath { * <li>{@code exp(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose exponential has to be computed. * @return the exponential of the argument. */ - public static native double exp(double d); + public static double exp(double x) { + double y, c, t; + double hi = 0, lo = 0; + int k = 0, xsb; + int hx; // should be unsigned, be careful! + final long bits = Double.doubleToRawLongBits(x); + int lowBits = (int) bits; + int highBits = (int) (bits >>> 32); + hx = highBits & 0x7fffffff; + xsb = (highBits >>> 31) & 1; + + /* filter out non-finite argument */ + if (hx >= 0x40862E42) { /* if |x|>=709.78... */ + if (hx >= 0x7ff00000) { + if (((hx & 0xfffff) | lowBits) != 0) { + return x + x; /* NaN */ + } else { + return (xsb == 0) ? x : 0.0; /* ieee_exp(+-inf)={inf,0} */ + } + } + + if (x > O_THRESHOLD) { + return HUGE * HUGE; /* overflow */ + } + + if (x < U_THRESHOLD) { + return TWOM1000 * TWOM1000; /* underflow */ + } + } + + /* argument reduction */ + if (hx > 0x3fd62e42) { /* if |x| > 0.5 ln2 */ + if (hx < 0x3FF0A2B2) { /* and |x| < 1.5 ln2 */ + hi = x - ((xsb == 0) ? 6.93147180369123816490e-01 : + -6.93147180369123816490e-01); // LN2HI[xsb]; + lo = (xsb == 0) ? 1.90821492927058770002e-10 : + -1.90821492927058770002e-10; // LN2LO[xsb]; + k = 1 - xsb - xsb; + } else { + k = (int) (INVLN2 * x + ((xsb == 0) ? 0.5 : -0.5 ));//halF[xsb]); + t = k; + hi = x - t * 6.93147180369123816490e-01; //ln2HI[0]; /* t*ln2HI is exact here */ + lo = t * 1.90821492927058770002e-10; //ln2LO[0]; + } + x = hi - lo; + } else if (hx < 0x3e300000) { /* when |x|<2**-28 */ + if (HUGE + x > 1.00000000000000000000e+00) + return 1.00000000000000000000e+00 + x;/* trigger inexact */ + } else { + k = 0; + } + + /* x is now in primary range */ + t = x * x; + c = x - t * (P1 + t * (P2 + t * (P3 + t * (P4 + t * P5)))); + if (k == 0) { + return 1.00000000000000000000e+00 - ((x * c) / (c - 2.0) - x); + } else { + y = 1.00000000000000000000e+00 - ((lo - (x * c) / (2.0 - c)) - hi); + } + long yBits = Double.doubleToRawLongBits(y); + if (k >= -1021) { + yBits += ((long) (k << 20)) << 32; /* add k to y's exponent */ + return Double.longBitsToDouble(yBits); + } else { + yBits += ((long) ((k + 1000) << 20)) << 32;/* add k to y's exponent */ + return Double.longBitsToDouble(yBits) * TWOM1000; + } + } + + private static final double TINY = 1.0e-300; + private static final double LN2_HI = 6.93147180369123816490e-01; + private static final double LN2_LO = 1.90821492927058770002e-10; + private static final double Q1 = -3.33333333333331316428e-02; + private static final double Q2 = 1.58730158725481460165e-03; + private static final double Q3 = -7.93650757867487942473e-05; + private static final double Q4 = 4.00821782732936239552e-06; + private static final double Q5 = -2.01099218183624371326e-07; /** * Returns the closest double approximation of <i>{@code e}</i><sup> @@ -295,17 +803,124 @@ public final class StrictMath { * <li>{@code expm1(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value to compute the <i>{@code e}</i><sup>{@code d}</sup> * {@code - 1} of. - * @return the <i>{@code e}</i><sup>{@code d}</sup>{@code - 1} value - * of the argument. + * @return the <i>{@code e}</i><sup>{@code d}</sup>{@code - 1} value of the + * argument. */ - public static native double expm1(double d); + public static double expm1(double x) { + double y, hi, lo, t, e, hxs, hfx, r1, c = 0.0; + int k, xsb; + long yBits = 0; + final long bits = Double.doubleToRawLongBits(x); + int highBits = (int) (bits >>> 32); + int lowBits = (int) (bits); + int hx = highBits & 0x7fffffff; // caution: should be unsigned! + xsb = highBits & 0x80000000; /* sign bit of x */ + y = xsb == 0 ? x : -x; /* y = |x| */ + + /* filter out huge and non-finite argument */ + if (hx >= 0x4043687A) { /* if |x|>=56*ln2 */ + if (hx >= 0x40862E42) { /* if |x|>=709.78... */ + if (hx >= 0x7ff00000) { + if (((hx & 0xfffff) | lowBits) != 0) { + return x + x; /* NaN */ + } else { + return (xsb == 0) ? x : -1.0;/* ieee_exp(+-inf)={inf,-1} */ + } + } + if (x > O_THRESHOLD) { + return HUGE * HUGE; /* overflow */ + } + } + if (xsb != 0) { /* x < -56*ln2, return -1.0 with inexact */ + if (x + TINY < 0.0) { /* raise inexact */ + return TINY - 1.00000000000000000000e+00; /* return -1 */ + } + } + } + /* argument reduction */ + if (hx > 0x3fd62e42) { /* if |x| > 0.5 ln2 */ + if (hx < 0x3FF0A2B2) { /* and |x| < 1.5 ln2 */ + if (xsb == 0) { + hi = x - LN2_HI; + lo = LN2_LO; + k = 1; + } else { + hi = x + LN2_HI; + lo = -LN2_LO; + k = -1; + } + } else { + k = (int) (INVLN2 * x + ((xsb == 0) ? 0.5 : -0.5)); + t = k; + hi = x - t * LN2_HI; /* t*ln2_hi is exact here */ + lo = t * LN2_LO; + } + x = hi - lo; + c = (hi - x) - lo; + } else if (hx < 0x3c900000) { /* when |x|<2**-54, return x */ + // t = huge+x; /* return x with inexact flags when x!=0 */ + // return x - (t-(huge+x)); + return x; // inexact flag is not set, but Java ignors this flag + // anyway + } else { + k = 0; + } + + /* x is now in primary range */ + hfx = 0.5 * x; + hxs = x * hfx; + r1 = 1.00000000000000000000e+00 + hxs * (Q1 + hxs * (Q2 + hxs * (Q3 + hxs * (Q4 + hxs * Q5)))); + t = 3.0 - r1 * hfx; + e = hxs * ((r1 - t) / (6.0 - x * t)); + if (k == 0) { + return x - (x * e - hxs); /* c is 0 */ + } else { + e = (x * (e - c) - c); + e -= hxs; + if (k == -1) { + return 0.5 * (x - e) - 0.5; + } + + if (k == 1) { + if (x < -0.25) { + return -2.0 * (e - (x + 0.5)); + } else { + return 1.00000000000000000000e+00 + 2.0 * (x - e); + } + } + + if (k <= -2 || k > 56) { /* suffice to return ieee_exp(x)-1 */ + y = 1.00000000000000000000e+00 - (e - x); + yBits = Double.doubleToRawLongBits(y); + yBits += (((long) k) << 52); /* add k to y's exponent */ + return Double.longBitsToDouble(yBits) - 1.00000000000000000000e+00; + } + + long tBits = Double.doubleToRawLongBits(1.00000000000000000000e+00) & 0x00000000ffffffffL; + + if (k < 20) { + tBits |= (((long) 0x3ff00000) - (0x200000 >> k)) << 32; + y = Double.longBitsToDouble(tBits) - (e - x); + yBits = Double.doubleToRawLongBits(y); + yBits += (((long) k) << 52); /* add k to y's exponent */ + return Double.longBitsToDouble(yBits); + } else { + tBits |= ((((long) 0x3ff) - k) << 52); /* 2^-k */ + y = x - (e + Double.longBitsToDouble(tBits)); + y += 1.00000000000000000000e+00; + yBits = Double.doubleToRawLongBits(y); + yBits += (((long) k) << 52); /* add k to y's exponent */ + return Double.longBitsToDouble(yBits); + } + } + } /** - * Returns the double conversion of the most positive (closest to - * positive infinity) integer less than or equal to the argument. + * Returns the double conversion of the most positive (closest to positive + * infinity) integer less than or equal to the argument. * <p> * Special cases: * <ul> @@ -319,9 +934,9 @@ public final class StrictMath { public static native double floor(double d); /** - * Returns {@code sqrt(}<i>{@code x}</i><sup>{@code 2}</sup>{@code +} - * <i> {@code y}</i><sup>{@code 2}</sup>{@code )}. The final result is - * without medium underflow or overflow. + * Returns {@code sqrt(}<i>{@code x}</i><sup>{@code 2}</sup>{@code +} <i> + * {@code y}</i><sup>{@code 2}</sup>{@code )}. The final result is without + * medium underflow or overflow. * <p> * Special cases: * <ul> @@ -369,6 +984,14 @@ public final class StrictMath { */ public static native double IEEEremainder(double x, double y); + private static final double LG1 = 6.666666666666735130e-01; + private static final double LG2 = 3.999999999940941908e-01; + private static final double LG3 = 2.857142874366239149e-01; + private static final double LG4 = 2.222219843214978396e-01; + private static final double LG5 = 1.818357216161805012e-01; + private static final double LG6 = 1.531383769920937332e-01; + private static final double LG7 = 1.479819860511658591e-01; + /** * Returns the closest double approximation of the natural logarithm of the * argument. @@ -383,11 +1006,95 @@ public final class StrictMath { * <li>{@code log(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose log has to be computed. * @return the natural logarithm of the argument. */ - public static native double log(double d); + public static double log(double x) { + double hfsq, f, s, z, R, w, t1, t2, dk; + int hx, i, j, k = 0; + int lx; // watch out, should be unsigned + + long bits = Double.doubleToRawLongBits(x); + hx = (int) (bits >>> 32); /* high word of x */ + lx = (int) bits; /* low word of x */ + + if (hx < 0x00100000) { /* x < 2**-1022 */ + if (((hx & 0x7fffffff) | lx) == 0) { + return -TWO54 / 0.0; /* ieee_log(+-0)=-inf */ + } + + if (hx < 0) { + return (x - x) / 0.0; /* ieee_log(-#) = NaN */ + } + + k -= 54; + x *= TWO54; /* subnormal number, scale up x */ + bits = Double.doubleToRawLongBits(x); + hx = (int) (bits >>> 32); /* high word of x */ + } + + if (hx >= 0x7ff00000) { + return x + x; + } + + k += (hx >> 20) - 1023; + hx &= 0x000fffff; + bits &= 0x00000000ffffffffL; + i = (hx + 0x95f64) & 0x100000; + bits |= ((long) hx | (i ^ 0x3ff00000)) << 32; /* normalize x or x/2 */ + x = Double.longBitsToDouble(bits); + k += (i >> 20); + f = x - 1.0; + + if ((0x000fffff & (2 + hx)) < 3) { /* |f| < 2**-20 */ + if (f == 0.0) { + if (k == 0) { + return 0.0; + } else { + dk = k; + } + return dk * LN2_HI + dk * LN2_LO; + } + + R = f * f * (0.5 - 0.33333333333333333 * f); + if (k == 0) { + return f - R; + } else { + dk = k; + return dk * LN2_HI - ((R - dk * LN2_LO) - f); + } + } + s = f / (2.0 + f); + dk = k; + z = s * s; + i = hx - 0x6147a; + w = z * z; + j = 0x6b851 - hx; + t1 = w * (LG2 + w * (LG4 + w * LG6)); + t2 = z * (LG1 + w * (LG3 + w * (LG5 + w * LG7))); + i |= j; + R = t2 + t1; + if (i > 0) { + hfsq = 0.5 * f * f; + if (k == 0) { + return f - (hfsq - s * (hfsq + R)); + } else { + return dk * LN2_HI + - ((hfsq - (s * (hfsq + R) + dk * LN2_LO)) - f); + } + } else { + if (k == 0) { + return f - s * (f - R); + } else { + return dk * LN2_HI - ((s * (f - R) - dk * LN2_LO) - f); + } + } + } + + private static final double IVLN10 = 4.34294481903251816668e-01; + private static final double LOG10_2HI = 3.01029995663611771306e-01; + private static final double LOG10_2LO = 3.69423907715893078616e-13; /** * Returns the closest double approximation of the base 10 logarithm of the @@ -403,11 +1110,54 @@ public final class StrictMath { * <li>{@code log10(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose base 10 log has to be computed. - * @return the natural logarithm of the argument. + * @return the the base 10 logarithm of x */ - public static native double log10(double d); + public static double log10(double x) { + double y, z; + int i, k = 0, hx; + int lx; // careful: lx should be unsigned! + long bits = Double.doubleToRawLongBits(x); + hx = (int) (bits >> 32); /* high word of x */ + lx = (int) bits; /* low word of x */ + if (hx < 0x00100000) { /* x < 2**-1022 */ + if (((hx & 0x7fffffff) | lx) == 0) { + return -TWO54 / 0.0; /* ieee_log(+-0)=-inf */ + } + + if (hx < 0) { + return (x - x) / 0.0; /* ieee_log(-#) = NaN */ + } + + k -= 54; + x *= TWO54; /* subnormal number, scale up x */ + bits = Double.doubleToRawLongBits(x); + hx = (int) (bits >> 32); /* high word of x */ + } + + if (hx >= 0x7ff00000) { + return x + x; + } + + k += (hx >> 20) - 1023; + i = (int) (((k & 0x00000000ffffffffL) & 0x80000000) >>> 31); + hx = (hx & 0x000fffff) | ((0x3ff - i) << 20); + y = k + i; + bits &= 0x00000000ffffffffL; + bits |= ((long) hx) << 32; + x = Double.longBitsToDouble(bits); // __HI(x) = hx; + z = y * LOG10_2LO + IVLN10 * log(x); + return z + y * LOG10_2HI; + } + + private static final double LP1 = 6.666666666666735130e-01, + LP2 = 3.999999999940941908e-01, /* 3FD99999 9997FA04 */ + LP3 = 2.857142874366239149e-01, /* 3FD24924 94229359 */ + LP4 = 2.222219843214978396e-01, /* 3FCC71C5 1D8E78AF */ + LP5 = 1.818357216161805012e-01, /* 3FC74664 96CB03DE */ + LP6 = 1.531383769920937332e-01, /* 3FC39A09 D078C69F */ + LP7 = 1.479819860511658591e-01; /* 3FC2F112 DF3E5244 */ /** * Returns the closest double approximation of the natural logarithm of the @@ -426,11 +1176,107 @@ public final class StrictMath { * <li>{@code log1p(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value to compute the {@code ln(1+d)} of. * @return the natural logarithm of the sum of the argument and 1. */ - public static native double log1p(double d); + + public static double log1p(double x) { + double hfsq, f = 0.0, c = 0.0, s, z, R, u = 0.0; + int k, hx, hu = 0, ax; + + final long bits = Double.doubleToRawLongBits(x); + hx = (int) (bits >>> 32); /* high word of x */ + ax = hx & 0x7fffffff; + + k = 1; + if (hx < 0x3FDA827A) { /* x < 0.41422 */ + if (ax >= 0x3ff00000) { /* x <= -1.0 */ + if (x == -1.0) { + return -TWO54 / 0.0; /* ieee_log1p(-1)=+inf */ + } else { + return (x - x) / (x - x); /* ieee_log1p(x<-1)=NaN */ + } + } + if (ax < 0x3e200000) { + if (TWO54 + x > 0.0 && ax < 0x3c900000) { + return x; + } else { + return x - x * x * 0.5; + } + } + if (hx > 0 || hx <= 0xbfd2bec3) { + k = 0; + f = x; + hu = 1; + } /* -0.2929<x<0.41422 */ + } + + if (hx >= 0x7ff00000) { + return x + x; + } + + if (k != 0) { + long uBits; + if (hx < 0x43400000) { + u = 1.0 + x; + uBits = Double.doubleToRawLongBits(u); + hu = (int) (uBits >>> 32); + k = (hu >> 20) - 1023; + c = (k > 0) ? 1.0 - (u - x) : x - (u - 1.0);/* correction term */ + c /= u; + } else { + uBits = Double.doubleToRawLongBits(x); + hu = (int) (uBits >>> 32); + k = (hu >> 20) - 1023; + c = 0; + } + hu &= 0x000fffff; + if (hu < 0x6a09e) { + // __HI(u) = hu|0x3ff00000; /* normalize u */ + uBits &= 0x00000000ffffffffL; + uBits |= ((long) hu | 0x3ff00000) << 32; + u = Double.longBitsToDouble(uBits); + } else { + k += 1; + // __HI(u) = hu|0x3fe00000; /* normalize u/2 */ + uBits &= 0xffffffffL; + uBits |= ((long) hu | 0x3fe00000) << 32; + u = Double.longBitsToDouble(uBits); + hu = (0x00100000 - hu) >> 2; + } + f = u - 1.0; + } + hfsq = 0.5 * f * f; + if (hu == 0) { /* |f| < 2**-20 */ + if (f == 0.0) { + if (k == 0) { + return 0.0; + } else { + c += k * LN2_LO; + return k * LN2_HI + c; + } + } + + R = hfsq * (1.0 - 0.66666666666666666 * f); + if (k == 0) { + return f - R; + } else { + return k * LN2_HI - ((R - (k * LN2_LO + c)) - f); + } + } + + s = f / (2.0 + f); + z = s * s; + R = z * (LP1 + z * (LP2 + z + * (LP3 + z * (LP4 + z * (LP5 + z * (LP6 + z * LP7)))))); + if (k == 0) { + return f - (hfsq - s * (hfsq + R)); + } else { + return k * LN2_HI + - ((hfsq - (s * (hfsq + R) + (k * LN2_LO + c))) - f); + } + } /** * Returns the most positive (closest to positive infinity) of the two @@ -453,8 +1299,8 @@ public final class StrictMath { if (d1 != d2) return Double.NaN; /* max( +0.0,-0.0) == +0.0 */ - if (d1 == 0.0 - && ((Double.doubleToLongBits(d1) & Double.doubleToLongBits(d2)) & 0x8000000000000000L) == 0) + if (d1 == 0.0 && + ((Double.doubleToLongBits(d1) & Double.doubleToLongBits(d2)) & 0x8000000000000000L) == 0) return 0.0; return d1; } @@ -480,8 +1326,8 @@ public final class StrictMath { if (f1 != f2) return Float.NaN; /* max( +0.0,-0.0) == +0.0 */ - if (f1 == 0.0f - && ((Float.floatToIntBits(f1) & Float.floatToIntBits(f2)) & 0x80000000) == 0) + if (f1 == 0.0f && + ((Float.floatToIntBits(f1) & Float.floatToIntBits(f2)) & 0x80000000) == 0) return 0.0f; return f1; } @@ -523,8 +1369,8 @@ public final class StrictMath { if (d1 != d2) return Double.NaN; /* min( +0.0,-0.0) == -0.0 */ - if (d1 == 0.0 - && ((Double.doubleToLongBits(d1) | Double.doubleToLongBits(d2)) & 0x8000000000000000l) != 0) + if (d1 == 0.0 && + ((Double.doubleToLongBits(d1) | Double.doubleToLongBits(d2)) & 0x8000000000000000l) != 0) return 0.0 * (-1.0); return d1; } @@ -550,8 +1396,8 @@ public final class StrictMath { if (f1 != f2) return Float.NaN; /* min( +0.0,-0.0) == -0.0 */ - if (f1 == 0.0f - && ((Float.floatToIntBits(f1) | Float.floatToIntBits(f2)) & 0x80000000) != 0) + if (f1 == 0.0f && + ((Float.floatToIntBits(f1) | Float.floatToIntBits(f2)) & 0x80000000) != 0) return 0.0f * (-1.0f); return f1; } @@ -706,7 +1552,7 @@ public final class StrictMath { * the value whose signum has to be computed. * @return the value of the signum function. */ - public static double signum(double d){ + public static double signum(double d) { return Math.signum(d); } @@ -729,10 +1575,12 @@ public final class StrictMath { * the value whose signum has to be computed. * @return the value of the signum function. */ - public static float signum(float f){ + public static float signum(float f) { return Math.signum(f); } + private static final double shuge = 1.0e307; + /** * Returns the closest double approximation of the hyperbolic sine of the * argument. @@ -746,11 +1594,57 @@ public final class StrictMath { * <li>{@code sinh(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose hyperbolic sine has to be computed. * @return the hyperbolic sine of the argument. */ - public static native double sinh(double d); + public static double sinh(double x) { + double t, w, h; + int ix, jx; + final long bits = Double.doubleToRawLongBits(x); + + jx = (int) (bits >>> 32); + ix = jx & 0x7fffffff; + + /* x is INF or NaN */ + if (ix >= 0x7ff00000) { + return x + x; + } + + h = 0.5; + if (jx < 0) { + h = -h; + } + + /* |x| in [0,22], return sign(x)*0.5*(E+E/(E+1))) */ + if (ix < 0x40360000) { /* |x|<22 */ + if (ix < 0x3e300000) /* |x|<2**-28 */ + if (shuge + x > 1.00000000000000000000e+00) { + return x;/* ieee_sinh(tiny) = tiny with inexact */ + } + t = expm1(Math.abs(x)); + if (ix < 0x3ff00000) + return h * (2.0 * t - t * t / (t + 1.00000000000000000000e+00)); + return h * (t + t / (t + 1.00000000000000000000e+00)); + } + + /* |x| in [22, ieee_log(maxdouble)] return 0.5*ieee_exp(|x|) */ + if (ix < 0x40862E42) { + return h * exp(Math.abs(x)); + } + + /* |x| in [log(maxdouble), overflowthresold] */ + final long lx = ((ONEBITS >>> 29) + ((int) bits)) & 0x00000000ffffffffL; + // lx = *( (((*(unsigned*)&one)>>29)) + (unsigned*)&x); + if (ix < 0x408633CE || (ix == 0x408633ce) && (lx <= 0x8fb9f87dL)) { + w = exp(0.5 * Math.abs(x)); + t = h * w; + return t * w; + } + + /* |x| > overflowthresold, ieee_sinh(x) overflow */ + return x * shuge; + } /** * Returns the closest double approximation of the sine of the argument. @@ -816,11 +1710,47 @@ public final class StrictMath { * <li>{@code tanh(NaN) = NaN}</li> * </ul> * - * @param d + * @param x * the value whose hyperbolic tangent has to be computed. * @return the hyperbolic tangent of the argument */ - public static native double tanh(double d); + public static double tanh(double x) { + double t, z; + int jx, ix; + + final long bits = Double.doubleToRawLongBits(x); + /* High word of |x|. */ + jx = (int) (bits >>> 32); + ix = jx & 0x7fffffff; + + /* x is INF or NaN */ + if (ix >= 0x7ff00000) { + if (jx >= 0) { + return 1.00000000000000000000e+00 / x + 1.00000000000000000000e+00; /* ieee_tanh(+-inf)=+-1 */ + } else { + return 1.00000000000000000000e+00 / x - 1.00000000000000000000e+00; /* ieee_tanh(NaN) = NaN */ + } + } + + /* |x| < 22 */ + if (ix < 0x40360000) { /* |x|<22 */ + if (ix < 0x3c800000) { /* |x|<2**-55 */ + return x * (1.00000000000000000000e+00 + x);/* ieee_tanh(small) = small */ + } + + if (ix >= 0x3ff00000) { /* |x|>=1 */ + t = Math.expm1(2.0 * Math.abs(x)); + z = 1.00000000000000000000e+00 - 2.0 / (t + 2.0); + } else { + t = Math.expm1(-2.0 * Math.abs(x)); + z = -t / (t + 2.0); + } + /* |x| > 22, return +-1 */ + } else { + z = 1.00000000000000000000e+00 - TINY; /* raised inexact flag */ + } + return (jx >= 0) ? z : -z; + } /** * Returns the measure in degrees of the supplied radian angle. The result @@ -922,6 +1852,7 @@ public final class StrictMath { /** * Returns a double with the given magnitude and the sign of {@code sign}. * If {@code sign} is NaN, the sign of the result is positive. + * * @since 1.6 */ public static double copySign(double magnitude, double sign) { @@ -932,13 +1863,15 @@ public final class StrictMath { // (Tested on a Nexus One.) long magnitudeBits = Double.doubleToRawLongBits(magnitude); long signBits = Double.doubleToRawLongBits((sign != sign) ? 1.0 : sign); - magnitudeBits = (magnitudeBits & ~Double.SIGN_MASK) | (signBits & Double.SIGN_MASK); + magnitudeBits = (magnitudeBits & ~Double.SIGN_MASK) + | (signBits & Double.SIGN_MASK); return Double.longBitsToDouble(magnitudeBits); } /** - * Returns a float with the given magnitude and the sign of {@code sign}. - * If {@code sign} is NaN, the sign of the result is positive. + * Returns a float with the given magnitude and the sign of {@code sign}. If + * {@code sign} is NaN, the sign of the result is positive. + * * @since 1.6 */ public static float copySign(float magnitude, float sign) { @@ -949,12 +1882,14 @@ public final class StrictMath { // (Tested on a Nexus One.) int magnitudeBits = Float.floatToRawIntBits(magnitude); int signBits = Float.floatToRawIntBits((sign != sign) ? 1.0f : sign); - magnitudeBits = (magnitudeBits & ~Float.SIGN_MASK) | (signBits & Float.SIGN_MASK); + magnitudeBits = (magnitudeBits & ~Float.SIGN_MASK) + | (signBits & Float.SIGN_MASK); return Float.intBitsToFloat(magnitudeBits); } /** * Returns the exponent of float {@code f}. + * * @since 1.6 */ public static int getExponent(float f) { @@ -963,14 +1898,17 @@ public final class StrictMath { /** * Returns the exponent of double {@code d}. + * * @since 1.6 */ - public static int getExponent(double d){ + public static int getExponent(double d) { return Math.getExponent(d); } /** - * Returns the next double after {@code start} in the given {@code direction}. + * Returns the next double after {@code start} in the given + * {@code direction}. + * * @since 1.6 */ public static double nextAfter(double start, double direction) { @@ -981,7 +1919,9 @@ public final class StrictMath { } /** - * Returns the next float after {@code start} in the given {@code direction}. + * Returns the next float after {@code start} in the given {@code direction} + * . + * * @since 1.6 */ public static float nextAfter(float start, double direction) { @@ -990,6 +1930,7 @@ public final class StrictMath { /** * Returns the next double larger than {@code d}. + * * @since 1.6 */ public static double nextUp(double d) { @@ -998,6 +1939,7 @@ public final class StrictMath { /** * Returns the next float larger than {@code f}. + * * @since 1.6 */ public static float nextUp(float f) { @@ -1006,6 +1948,7 @@ public final class StrictMath { /** * Returns {@code d} * 2^{@code scaleFactor}. The result may be rounded. + * * @since 1.6 */ public static double scalb(double d, int scaleFactor) { @@ -1049,12 +1992,10 @@ public final class StrictMath { } else { if (Math.abs(d) >= Double.MIN_NORMAL) { // common situation - result = ((factor + Double.EXPONENT_BIAS) << Double.MANTISSA_BITS) - | (bits & Double.MANTISSA_MASK); + result = ((factor + Double.EXPONENT_BIAS) << Double.MANTISSA_BITS) | (bits & Double.MANTISSA_MASK); } else { // origin d is sub-normal, change mantissa to normal style - result = ((factor + Double.EXPONENT_BIAS) << Double.MANTISSA_BITS) - | ((bits << (subNormalFactor + 1)) & Double.MANTISSA_MASK); + result = ((factor + Double.EXPONENT_BIAS) << Double.MANTISSA_BITS) | ((bits << (subNormalFactor + 1)) & Double.MANTISSA_MASK); } } return Double.longBitsToDouble(result | sign); @@ -1062,6 +2003,7 @@ public final class StrictMath { /** * Returns {@code d} * 2^{@code scaleFactor}. The result may be rounded. + * * @since 1.6 */ public static float scalb(float d, int scaleFactor) { @@ -1073,8 +2015,7 @@ public final class StrictMath { int factor = ((bits & Float.EXPONENT_MASK) >> Float.MANTISSA_BITS) - Float.EXPONENT_BIAS + scaleFactor; // calculates the factor of sub-normal values - int subNormalFactor = Integer.numberOfLeadingZeros(bits & ~Float.SIGN_MASK) - - Float.EXPONENT_BITS; + int subNormalFactor = Integer.numberOfLeadingZeros(bits & ~Float.SIGN_MASK) - Float.EXPONENT_BITS; if (subNormalFactor < 0) { // not sub-normal values subNormalFactor = 0; @@ -1105,8 +2046,9 @@ public final class StrictMath { | (bits & Float.MANTISSA_MASK); } else { // origin d is sub-normal, change mantissa to normal style - result = ((factor + Float.EXPONENT_BIAS) << Float.MANTISSA_BITS) - | ((bits << (subNormalFactor + 1)) & Float.MANTISSA_MASK); + result = ((factor + Float.EXPONENT_BIAS) + << Float.MANTISSA_BITS) | ( + (bits << (subNormalFactor + 1)) & Float.MANTISSA_MASK); } } return Float.intBitsToFloat(result | sign); @@ -1120,10 +2062,10 @@ public final class StrictMath { } // change it to positive int absDigits = -digits; - if (Integer.numberOfLeadingZeros(bits & ~Float.SIGN_MASK) <= (32 - absDigits)) { + if (Integer.numberOfLeadingZeros(bits & ~Float.SIGN_MASK) + <= (32 - absDigits)) { // some bits will remain after shifting, calculates its carry - if ((((bits >> (absDigits - 1)) & 0x1) == 0) - || Integer.numberOfTrailingZeros(bits) == (absDigits - 1)) { + if ((((bits >> (absDigits - 1)) & 0x1) == 0) || Integer.numberOfTrailingZeros(bits) == (absDigits - 1)) { return bits >> absDigits; } return ((bits >> absDigits) + 1); @@ -1139,10 +2081,10 @@ public final class StrictMath { } // change it to positive long absDigits = -digits; - if (Long.numberOfLeadingZeros(bits & ~Double.SIGN_MASK) <= (64 - absDigits)) { + if (Long.numberOfLeadingZeros(bits & ~Double.SIGN_MASK) + <= (64 - absDigits)) { // some bits will remain after shifting, calculates its carry - if ((((bits >> (absDigits - 1)) & 0x1) == 0) - || Long.numberOfTrailingZeros(bits) == (absDigits - 1)) { + if ((((bits >> (absDigits - 1)) & 0x1) == 0) || Long.numberOfTrailingZeros(bits) == (absDigits - 1)) { return bits >> absDigits; } return ((bits >> absDigits) + 1); diff --git a/luni/src/main/java/java/lang/System.java b/luni/src/main/java/java/lang/System.java index 9425894..e79f844 100644 --- a/luni/src/main/java/java/lang/System.java +++ b/luni/src/main/java/java/lang/System.java @@ -50,6 +50,7 @@ import java.nio.channels.spi.SelectorProvider; import java.util.AbstractMap; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -108,6 +109,33 @@ public final class System { unchangeableSystemProperties = initUnchangeableSystemProperties(); systemProperties = createSystemProperties(); lineSeparator = System.getProperty("line.separator"); + + addLegacyLocaleSystemProperties(); + } + + private static void addLegacyLocaleSystemProperties() { + final String locale = getProperty("user.locale", ""); + if (!locale.isEmpty()) { + Locale l = Locale.forLanguageTag(locale); + setUnchangeableSystemProperty("user.language", l.getLanguage()); + setUnchangeableSystemProperty("user.region", l.getCountry()); + setUnchangeableSystemProperty("user.variant", l.getVariant()); + } else { + // If "user.locale" isn't set we fall back to our old defaults of + // language="en" and region="US" (if unset) and don't attempt to set it. + // The Locale class will fall back to using user.language and + // user.region if unset. + final String language = getProperty("user.language", ""); + final String region = getProperty("user.region", ""); + + if (language.isEmpty()) { + setUnchangeableSystemProperty("user.language", "en"); + } + + if (region.isEmpty()) { + setUnchangeableSystemProperty("user.region", "US"); + } + } } /** @@ -751,8 +779,6 @@ public final class System { p.put("java.vm.vendor.url", projectUrl); p.put("file.encoding", "UTF-8"); - p.put("user.language", "en"); - p.put("user.region", "US"); try { StructPasswd passwd = Libcore.os.getpwuid(Libcore.os.getuid()); @@ -771,6 +797,13 @@ public final class System { p.put("android.icu.unicode.version", ICU.getUnicodeVersion()); p.put("android.icu.cldr.version", ICU.getCldrVersion()); + // Property override for ICU4J : this is the location of the ICU4C data. This + // is prioritized over the properties in ICUConfig.properties. The issue with using + // that is that it doesn't play well with jarjar and it needs complicated build rules + // to change its default value. + String icuDataPath = generateIcuDataPath(); + p.put("android.icu.impl.ICUBinary.dataPath", icuDataPath); + parsePropertyAssignments(p, specialProperties()); // Override built-in properties with settings from the command line. @@ -780,9 +813,13 @@ public final class System { /** * Inits an unchangeable system property with the given value. - * This is useful when the environment needs to change under native bridge emulation. + * + * This is called from native code when the environment needs to change under native + * bridge emulation. + * + * @hide also visible for tests. */ - private static void initUnchangeableSystemProperty(String name, String value) { + public static void setUnchangeableSystemProperty(String name, String value) { checkPropertyName(name); unchangeableSystemProperties.put(name, value); } @@ -807,6 +844,37 @@ public final class System { return p; } + private static String generateIcuDataPath() { + StringBuilder icuDataPathBuilder = new StringBuilder(); + // ICU should first look in ANDROID_DATA. This is used for (optional) timezone data. + String dataIcuDataPath = getEnvironmentPath("ANDROID_DATA", "/misc/zoneinfo/current/icu"); + if (dataIcuDataPath != null) { + icuDataPathBuilder.append(dataIcuDataPath); + } + + // ICU should always look in ANDROID_ROOT. + String systemIcuDataPath = getEnvironmentPath("ANDROID_ROOT", "/usr/icu"); + if (systemIcuDataPath != null) { + if (icuDataPathBuilder.length() > 0) { + icuDataPathBuilder.append(":"); + } + icuDataPathBuilder.append(systemIcuDataPath); + } + return icuDataPathBuilder.toString(); + } + + /** + * Creates a path by combining the value of an environment variable with a relative path. + * Returns {@code null} if the environment variable is not set. + */ + private static String getEnvironmentPath(String environmentVariable, String path) { + String variable = getenv(environmentVariable); + if (variable == null) { + return null; + } + return variable + path; + } + /** * Returns an array of "key=value" strings containing information not otherwise * easily available, such as #defined library versions. @@ -964,10 +1032,12 @@ public final class System { public static native int identityHashCode(Object anObject); /** - * Returns the system's line separator. On Android, this is {@code "\n"}. The value - * comes from the value of the {@code line.separator} system property when the VM - * starts. Later changes to the property will not affect the value returned by this - * method. + * Returns the system's line separator. On Android, this is {@code "\n"}. The value comes from + * the value of the {@code line.separator} system property. + * + * <p>On Android versions before Lollipop the {@code line.separator} system property can be + * modified but this method continues to return the original value. The system property cannot + * be modified on later versions of Android. * @since 1.7 */ public static String lineSeparator() { @@ -1106,7 +1176,12 @@ public final class System { * named by the argument. On Android, this would turn {@code "MyLibrary"} into * {@code "libMyLibrary.so"}. */ - public static native String mapLibraryName(String nickname); + public static String mapLibraryName(String nickname) { + if (nickname == null) { + throw new NullPointerException("nickname == null"); + } + return "lib" + nickname + ".so"; + } /** * Used to set System.err, System.in, and System.out. diff --git a/luni/src/main/java/java/lang/reflect/Modifier.java b/luni/src/main/java/java/lang/reflect/Modifier.java index 257064e..0480b8b 100644 --- a/luni/src/main/java/java/lang/reflect/Modifier.java +++ b/luni/src/main/java/java/lang/reflect/Modifier.java @@ -302,4 +302,23 @@ public class Modifier { buf.setLength(buf.length() - 1); return buf.toString(); } + + /** + * Returns the modifiers for fields that can be present in a declaration. + * @hide + */ + static String getDeclarationFieldModifiers(int modifiers) { + return Modifier.toString(modifiers & fieldModifiers()); + } + + /** + * Returns the modifiers for methods that can be present in a declaration. + * @hide + */ + static String getDeclarationMethodModifiers(int modifiers) { + return Modifier.toString(modifiers & ( + Modifier.isConstructor(modifiers) + ? Modifier.constructorModifiers() + : Modifier.methodModifiers())); + } } diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java index f9b72d8..3195240 100644 --- a/luni/src/main/java/java/net/DatagramSocket.java +++ b/luni/src/main/java/java/net/DatagramSocket.java @@ -56,7 +56,7 @@ public class DatagramSocket implements Closeable { /** * Constructs a UDP datagram socket which is bound to any available port on - * the localhost. + * the local host using a wildcard address. * * @throws SocketException * if an error occurs while creating or binding the socket. @@ -67,34 +67,34 @@ public class DatagramSocket implements Closeable { /** * Constructs a UDP datagram socket which is bound to the specific port - * {@code aPort} on the localhost. Valid values for {@code aPort} are + * {@code aPort} on the local host using a wildcard address. Valid values for {@code aPort} are * between 0 and 65535 inclusive. * * @param aPort - * the port to bind on the localhost. + * the port to bind on the local host. * @throws SocketException * if an error occurs while creating or binding the socket. */ public DatagramSocket(int aPort) throws SocketException { checkPort(aPort); - createSocket(aPort, Inet4Address.ANY); + createSocket(aPort, Inet6Address.ANY); } /** - * Constructs a UDP datagram socket which is bound to the specific local - * address {@code addr} on port {@code aPort}. Valid values for {@code - * aPort} are between 0 and 65535 inclusive. + * Constructs a UDP datagram socket which is bound to the specific local address {@code addr} on + * port {@code aPort}. Valid values for {@code aPort} are between 0 and 65535 inclusive. If + * {@code addr} is {@code null} the socket will be bound to a wildcard address. * * @param aPort - * the port to bind on the localhost. + * the port to bind on the local host. * @param addr - * the address to bind on the localhost. + * the address to bind on the local host. * @throws SocketException * if an error occurs while creating or binding the socket. */ public DatagramSocket(int aPort, InetAddress addr) throws SocketException { checkPort(aPort); - createSocket(aPort, (addr == null) ? Inet4Address.ANY : addr); + createSocket(aPort, (addr == null) ? Inet6Address.ANY : addr); } private void checkPort(int aPort) { @@ -443,7 +443,7 @@ public class DatagramSocket implements Closeable { private void ensureBound() throws SocketException { if (!isBound()) { - impl.bind(0, Inet4Address.ANY); + impl.bind(0, Inet6Address.ANY); isBound = true; } } @@ -467,7 +467,7 @@ public class DatagramSocket implements Closeable { InetAddress addr; if (localAddr == null) { localPort = 0; - addr = Inet4Address.ANY; + addr = Inet6Address.ANY; } else { if (!(localAddr instanceof InetSocketAddress)) { throw new IllegalArgumentException("Local address not an InetSocketAddress: " + diff --git a/luni/src/main/java/java/net/DatagramSocketImpl.java b/luni/src/main/java/java/net/DatagramSocketImpl.java index 1a39987..c5e7d70 100644 --- a/luni/src/main/java/java/net/DatagramSocketImpl.java +++ b/luni/src/main/java/java/net/DatagramSocketImpl.java @@ -43,13 +43,13 @@ public abstract class DatagramSocketImpl implements SocketOptions { } /** - * Binds the datagram socket to the given localhost/port. Sockets must be + * Binds the datagram socket to the given local host/port. Sockets must be * bound prior to attempting to send or receive data. * * @param port - * the port on the localhost to bind. + * the port on the local host to bind to. * @param addr - * the address on the multihomed localhost to bind. + * the address on the multihomed local host to bind to. * @throws SocketException * if an error occurs while binding, for example, if the port * has been already bound. diff --git a/luni/src/main/java/java/net/Inet6Address.java b/luni/src/main/java/java/net/Inet6Address.java index 8ab0f8d..347f43a 100644 --- a/luni/src/main/java/java/net/Inet6Address.java +++ b/luni/src/main/java/java/net/Inet6Address.java @@ -43,7 +43,7 @@ public final class Inet6Address extends InetAddress { */ public static final InetAddress LOOPBACK = new Inet6Address(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, - "localhost", 0); + "ip6-localhost", 0); private boolean scope_id_set; private int scope_id; diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java index 5cfa15a..581e1bd 100644 --- a/luni/src/main/java/java/net/InetAddress.java +++ b/luni/src/main/java/java/net/InetAddress.java @@ -202,10 +202,10 @@ public class InetAddress implements Serializable { /** * Gets all IP addresses associated with the given {@code host} identified * by name or literal IP address. The IP address is resolved by the - * configured name service. If the host name is empty or {@code null} an - * {@code UnknownHostException} is thrown. If the host name is a literal IP - * address string an array with the corresponding single {@code InetAddress} - * is returned. + * configured name service. If the host name is empty or {@code null} the + * IP addresses of the loopback interfaces are returned. If the host name + * is a literal IP address string an array with the corresponding single + * {@code InetAddress} is returned. * * @param host the hostname or literal IP string to be resolved. * @return the array of addresses associated with the specified host. @@ -344,6 +344,19 @@ public class InetAddress implements Serializable { } /** + * Returns the hostname if known, or the result of {@code #getHostAddress}. + * Unlike {@link #getHostName}, this method will never cause a DNS lookup. + * + * @hide For libcore situations that must avoid DNS lookups. + */ + public String getHostString() { + if (hostName == null) { + return getHostAddress(); + } + return hostName; + } + + /** * Returns the fully qualified hostname corresponding to this IP address. */ public String getCanonicalHostName() { diff --git a/luni/src/main/java/java/net/InetSocketAddress.java b/luni/src/main/java/java/net/InetSocketAddress.java index a366133..1b3eabe 100644 --- a/luni/src/main/java/java/net/InetSocketAddress.java +++ b/luni/src/main/java/java/net/InetSocketAddress.java @@ -21,7 +21,7 @@ import java.io.IOException; import java.io.ObjectInputStream; /** - * This class represents a socket endpoint described by a IP address and a port + * This class represents the address of a socket endpoint described by a IP address and a port * number. It is a concrete implementation of {@code SocketAddress} for IP. */ public class InetSocketAddress extends SocketAddress { @@ -48,8 +48,7 @@ public class InetSocketAddress extends SocketAddress { * no specified address. The range for valid port numbers is between 0 and * 65535 inclusive. * - * @param port - * the specified port number to which this socket is bound. + * @param port the port number of the socket endpoint. */ public InetSocketAddress(int port) { this((InetAddress) null, port); @@ -58,19 +57,17 @@ public class InetSocketAddress extends SocketAddress { /** * Creates a socket endpoint with the given port number {@code port} and * {@code address}. The range for valid port numbers is between 0 and 65535 - * inclusive. If {@code address} is {@code null} this socket is bound to the - * IPv4 wildcard address. + * inclusive. If {@code address} is {@code null} the address is set to a + * wildcard address. * - * @param port - * the specified port number to which this socket is bound. - * @param address - * the specified address to which this socket is bound. + * @param address the address of the socket endpoint. + * @param port the port number of the socket endpoint. */ public InetSocketAddress(InetAddress address, int port) { if (port < 0 || port > 65535) { throw new IllegalArgumentException("port=" + port); } - this.addr = (address == null) ? Inet4Address.ANY : address; + this.addr = (address == null) ? Inet6Address.ANY : address; this.hostname = null; this.port = port; } @@ -81,10 +78,8 @@ public class InetSocketAddress extends SocketAddress { * {@code null}. The range for valid port numbers is between 0 and 65535 * inclusive. * - * @param port - * the specified port number to which this socket is bound. - * @param host - * the specified hostname to which this socket is bound. + * @param host the hostname of the socket endpoint. + * @param port the port number of the socket endpoint. */ public InetSocketAddress(String host, int port) { this(host, port, true); @@ -117,28 +112,25 @@ public class InetSocketAddress extends SocketAddress { * hostname into an {@code InetAddress}. The address field is marked as * unresolved. * - * @param host - * the specified hostname to which this socket is bound. - * @param port - * the specified port number to which this socket is bound. + * @param host the hostname of the socket endpoint. + * @param port the port number of the socket endpoint. * @return the created InetSocketAddress instance. - * @throws IllegalArgumentException - * if the hostname {@code host} is {@code null} or the port is - * not in the range between 0 and 65535. + * @throws IllegalArgumentException if the hostname {@code host} is {@code null} or the port is + * not in the range between 0 and 65535. */ public static InetSocketAddress createUnresolved(String host, int port) { return new InetSocketAddress(host, port, false); } /** - * Returns this socket address' port. + * Returns the socket endpoint's port. */ public final int getPort() { return port; } /** - * Returns this socket address' address. + * Returns the socket endpoint's address. */ public final InetAddress getAddress() { return addr; @@ -159,7 +151,7 @@ public class InetSocketAddress extends SocketAddress { * @since 1.7 */ public final String getHostString() { - return (hostname != null) ? hostname : addr.getHostAddress(); + return (hostname != null) ? hostname : addr.getHostString(); } /** @@ -186,9 +178,8 @@ public class InetSocketAddress extends SocketAddress { * socket endpoints are equal if the IP address or the hostname of both are * equal and they are bound to the same port. * - * @param socketAddr - * the object to be tested for equality. - * @return {@code true} if this socket and the given socket object {@code + * @param socketAddr the object to be tested for equality. + * @return {@code true} if this socket endpoint and the given socket endpoint {@code * socketAddr} are equal, {@code false} otherwise. */ @Override diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java index 4e5ba44..0c50f62 100644 --- a/luni/src/main/java/java/net/PlainSocketImpl.java +++ b/luni/src/main/java/java/net/PlainSocketImpl.java @@ -301,16 +301,12 @@ public class PlainSocketImpl extends SocketImpl { * Gets the InetAddress of the SOCKS proxy server. */ private InetAddress socksGetServerAddress() throws UnknownHostException { - String proxyName; // get socks server address from proxy. It is unnecessary to check // "socksProxyHost" property, since all proxy setting should be // determined by ProxySelector. - InetSocketAddress addr = (InetSocketAddress) proxy.address(); - proxyName = addr.getHostName(); - if (proxyName == null) { - proxyName = addr.getAddress().getHostAddress(); - } - return InetAddress.getByName(proxyName); + InetSocketAddress socketAddress = (InetSocketAddress) proxy.address(); + InetAddress address = socketAddress.getAddress(); + return (address != null) ? address : InetAddress.getByName(socketAddress.getHostName()); } /** diff --git a/luni/src/main/java/java/net/ServerSocket.java b/luni/src/main/java/java/net/ServerSocket.java index 72b197f..a2cd9c6 100644 --- a/luni/src/main/java/java/net/ServerSocket.java +++ b/luni/src/main/java/java/net/ServerSocket.java @@ -65,24 +65,25 @@ public class ServerSocket implements Closeable { } /** - * Constructs a new {@code ServerSocket} instance bound to the given {@code port}. - * The backlog is set to 50. If {@code port == 0}, a port will be assigned by the OS. + * Constructs a new {@code ServerSocket} instance bound to the given {@code port} using a + * wildcard address. The backlog is set to 50. If {@code port == 0}, a port will be assigned by + * the OS. * * @throws IOException if an error occurs while creating the socket. */ public ServerSocket(int port) throws IOException { - this(port, DEFAULT_BACKLOG, Inet4Address.ANY); + this(port, DEFAULT_BACKLOG, Inet6Address.ANY); } /** - * Constructs a new {@code ServerSocket} instance bound to the given {@code port}. - * The backlog is set to {@code backlog}. + * Constructs a new {@code ServerSocket} instance bound to the given {@code port} using a + * wildcard address. The backlog is set to {@code backlog}. * If {@code port == 0}, a port will be assigned by the OS. * * @throws IOException if an error occurs while creating the socket. */ public ServerSocket(int port, int backlog) throws IOException { - this(port, backlog, Inet4Address.ANY); + this(port, backlog, Inet6Address.ANY); } /** @@ -97,7 +98,7 @@ public class ServerSocket implements Closeable { checkListen(port); this.impl = factory != null ? factory.createSocketImpl() : new PlainServerSocketImpl(); - InetAddress addr = (localAddress == null) ? Inet4Address.ANY : localAddress; + InetAddress addr = (localAddress == null) ? Inet6Address.ANY : localAddress; synchronized (this) { impl.create(true); @@ -316,7 +317,7 @@ public class ServerSocket implements Closeable { InetAddress addr; int port; if (localAddr == null) { - addr = Inet4Address.ANY; + addr = Inet6Address.ANY; port = 0; } else { if (!(localAddr instanceof InetSocketAddress)) { diff --git a/luni/src/main/java/java/net/Socket.java b/luni/src/main/java/java/net/Socket.java index 5dd350a..7ee6b05 100644 --- a/luni/src/main/java/java/net/Socket.java +++ b/luni/src/main/java/java/net/Socket.java @@ -41,7 +41,7 @@ public class Socket implements Closeable { private boolean isInputShutdown = false; private boolean isOutputShutdown = false; - private InetAddress localAddress = Inet4Address.ANY; + private InetAddress localAddress = Inet6Address.ANY; private final Object connectLock = new Object(); @@ -211,7 +211,7 @@ public class Socket implements Closeable { /** * Creates a new streaming socket connected to the target host specified by * the parameters {@code dstAddress} and {@code dstPort}. The socket is - * bound to any available port on the local host. + * bound to any available port on the local host using a wildcard address. * * @param dstAddress * the target host address to connect to. @@ -228,16 +228,18 @@ public class Socket implements Closeable { /** * Creates a new streaming socket connected to the target host specified by - * the parameters {@code dstAddress} and {@code dstPort}. On the local - * endpoint the socket is bound to the given address {@code localAddress} on - * port {@code localPort}. + * the parameters {@code dstAddress} and {@code dstPort}. + * + * <p>On the local endpoint the socket is bound to the given address {@code localAddress} on + * port {@code localPort}. If {@code localAddress} is {@code null} the socket will be bound to a + * wildcard address. * * @param dstAddress * the target host address to connect to. * @param dstPort * the port on the target host to connect to. * @param localAddress - * the address on the local host to bind to. + * the address on the local host to bind to, or null. * @param localPort * the port on the local host to bind to. * @throws IOException @@ -253,7 +255,7 @@ public class Socket implements Closeable { /** * Creates a new streaming or datagram socket connected to the target host * specified by the parameters {@code addr} and {@code port}. The socket is - * bound to any available port on the local host. + * bound to any available port on the local host using a wildcard address. * * @param addr * the Internet address to connect to. @@ -315,7 +317,7 @@ public class Socket implements Closeable { isConnected = false; // RI compatibility: the RI returns the any address (but the original local port) after // close. - localAddress = Inet4Address.ANY; + localAddress = Inet6Address.ANY; impl.close(); } @@ -330,7 +332,7 @@ public class Socket implements Closeable { isConnected = false; // RI compatibility: the RI returns the any address (but the original local port) after // close. - localAddress = Inet4Address.ANY; + localAddress = Inet6Address.ANY; impl.onClose(); } @@ -577,7 +579,7 @@ public class Socket implements Closeable { throw new IllegalArgumentException("Local port out of range: " + localPort); } - InetAddress addr = localAddress == null ? Inet4Address.ANY : localAddress; + InetAddress addr = localAddress == null ? Inet6Address.ANY : localAddress; synchronized (this) { impl.create(streaming); isCreated = true; @@ -772,7 +774,7 @@ public class Socket implements Closeable { InetAddress addr; if (localAddr == null) { port = 0; - addr = Inet4Address.ANY; + addr = Inet6Address.ANY; } else { if (!(localAddr instanceof InetSocketAddress)) { throw new IllegalArgumentException("Local address not an InetSocketAddress: " + @@ -875,7 +877,7 @@ public class Socket implements Closeable { // options on create // impl.create(true); if (!usingSocks()) { - impl.bind(Inet4Address.ANY, 0); + impl.bind(Inet6Address.ANY, 0); } isBound = true; } diff --git a/luni/src/main/java/java/net/URL.java b/luni/src/main/java/java/net/URL.java index dd487bc..b62ed84 100644 --- a/luni/src/main/java/java/net/URL.java +++ b/luni/src/main/java/java/net/URL.java @@ -331,7 +331,7 @@ public final class URL implements Serializable { * Virtual hosting permits unrelated sites to share an IP address. This * method could report two otherwise unrelated URLs to be equal because * they're hosted on the same server.</li> - * <li><strong>The network many not be available.</strong> Two URLs could be + * <li><strong>The network may not be available.</strong> Two URLs could be * equal when a network is available and unequal otherwise.</li> * <li><strong>The network may change.</strong> The IP address for a given * host name varies by network and over time. This is problematic for mobile diff --git a/luni/src/main/java/java/nio/SelectorImpl.java b/luni/src/main/java/java/nio/SelectorImpl.java index 45406b1..c73ba22 100644 --- a/luni/src/main/java/java/nio/SelectorImpl.java +++ b/luni/src/main/java/java/nio/SelectorImpl.java @@ -38,7 +38,6 @@ import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; -import static android.system.OsConstants.EINTR; import static android.system.OsConstants.POLLERR; import static android.system.OsConstants.POLLHUP; import static android.system.OsConstants.POLLIN; @@ -94,7 +93,7 @@ final class SelectorImpl extends AbstractSelector { * configure the pipe so we can fully drain it without blocking. */ try { - FileDescriptor[] pipeFds = Libcore.os.pipe(); + FileDescriptor[] pipeFds = Libcore.os.pipe2(0); wakeupIn = pipeFds[0]; wakeupOut = pipeFds[1]; IoUtils.setBlocking(wakeupIn, false); @@ -183,9 +182,7 @@ final class SelectorImpl extends AbstractSelector { try { rc = Libcore.os.poll(pollFds.array(), (int) timeout); } catch (ErrnoException errnoException) { - if (errnoException.errno != EINTR) { - throw errnoException.rethrowAsIOException(); - } + throw errnoException.rethrowAsIOException(); } } finally { if (isBlocking) { diff --git a/luni/src/main/java/java/nio/channels/Selector.java b/luni/src/main/java/java/nio/channels/Selector.java index 6d9b063..baa6a7f 100644 --- a/luni/src/main/java/java/nio/channels/Selector.java +++ b/luni/src/main/java/java/nio/channels/Selector.java @@ -78,8 +78,11 @@ public abstract class Selector implements Closeable { public abstract boolean isOpen(); /** - * Gets the set of registered keys. The set is immutable and is not thread- - * safe. + * Gets the set of registered keys. + * + * <p>The returned set cannot be changed directly but can be modified + * indirectly by operations on the Selector. It should therefore not be + * treated as thread-safe. * * @return the set of registered keys. */ @@ -127,9 +130,11 @@ public abstract class Selector implements Closeable { public abstract int select(long timeout) throws IOException; /** - * Gets the selection keys whose channels are ready for operation. The set - * is not thread-safe and no keys may be added to it. Removing keys is - * allowed. + * Gets the selection keys whose channels are ready for operation. + * + * <p>Keys cannot be added to the set directly. Keys can be removed. + * The set can be modified indirectly by operations on the Selector. It + * should therefore not be treated as thread-safe. * * @return the selection keys whose channels are ready for operation. * @throws ClosedSelectorException diff --git a/luni/src/main/java/java/nio/charset/CharsetEncoder.java b/luni/src/main/java/java/nio/charset/CharsetEncoder.java index 9217bba..9d53328 100644 --- a/luni/src/main/java/java/nio/charset/CharsetEncoder.java +++ b/luni/src/main/java/java/nio/charset/CharsetEncoder.java @@ -194,13 +194,19 @@ public abstract class CharsetEncoder { throw illegalStateException(); } + if (!cb.hasRemaining()) { + return true; + } + CodingErrorAction originalMalformedInputAction = malformedInputAction; CodingErrorAction originalUnmappableCharacterAction = unmappableCharacterAction; onMalformedInput(CodingErrorAction.REPORT); onUnmappableCharacter(CodingErrorAction.REPORT); try { - encode(cb); - return true; + ByteBuffer buf = encode(cb); + // b/18474439: ICU will return U_ZERO_ERROR but produce an output buffer + // of size zero when it encounters an ignorable codepoint. + return buf.hasRemaining(); } catch (CharacterCodingException e) { return false; } finally { @@ -436,24 +442,33 @@ public abstract class CharsetEncoder { * <p> * This method will call {@link #implFlush(ByteBuffer) implFlush}. Some * encoders may need to write some bytes to the output buffer when they have - * read all input characters, subclasses can overridden - * {@link #implFlush(ByteBuffer) implFlush} to perform writing action. + * read all input characters. Subclasses can override + * {@link #implFlush(ByteBuffer) implFlush} to perform any writes that are + * required at the end of the output sequence, such as footers and other + * metadata. * <p> - * The maximum number of written bytes won't larger than - * {@link ByteBuffer#remaining() out.remaining()}. If some encoder wants to + * The maximum number of written bytes won't be larger than + * {@link ByteBuffer#remaining() out.remaining()}. If the encoder wants to * write more bytes than the output buffer's available remaining space, then - * <code>CoderResult.OVERFLOW</code> will be returned, and this method - * must be called again with a byte buffer that has free space. Otherwise - * this method will return <code>CoderResult.UNDERFLOW</code>, which - * means one encoding process has been completed successfully. + * it will return {@code CoderResult.OVERFLOW}. This method must then be + * called again with a byte buffer that has free space. + * <p> + * If the encoder was asked to flush its output when its input is incomplete, + * (because it ends with an unpaired surrogate, say) it may return + * {@code CodeResult.MALFORMED}. + * <p> + * In all other cases the encoder will return {@code CoderResult.UNDERFLOW}, + * which signifies that all the input so far has been successfully encoded. * <p> * During the flush, the output buffer's position will be changed * accordingly, while its mark and limit will be intact. + * <p> + * This method is a no-op if the encoder has already been flushed. * - * @param out - * the given output buffer. - * @return <code>CoderResult.UNDERFLOW</code> or - * <code>CoderResult.OVERFLOW</code>. + * @param out the given output buffer. + * @return {@code CoderResult.UNDERFLOW} or + * {@code CoderResult.OVERFLOW} or + * {@code CoderResult.MALFORMED} * @throws IllegalStateException * if this encoder isn't already flushed or at end of input. */ @@ -461,6 +476,9 @@ public abstract class CharsetEncoder { if (state != FLUSHED && state != END_OF_INPUT) { throw illegalStateException(); } + if (state == FLUSHED) { + return CoderResult.UNDERFLOW; + } CoderResult result = implFlush(out); if (result == CoderResult.UNDERFLOW) { state = FLUSHED; diff --git a/luni/src/main/java/java/security/Security.java b/luni/src/main/java/java/security/Security.java index b859f9a..aeb189f 100644 --- a/luni/src/main/java/java/security/Security.java +++ b/luni/src/main/java/java/security/Security.java @@ -19,6 +19,8 @@ package java.security; import java.io.BufferedInputStream; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; @@ -47,16 +49,25 @@ public final class Security { // - load security properties files // - load statically registered providers // - if no provider description file found then load default providers + // Note: Getting the input stream for the security.properties file is factored into its own + // function, which will be intercepted during boot image creation. static { boolean loaded = false; + Reader input = null; try { - InputStream configStream = Security.class.getResourceAsStream("security.properties"); - InputStream input = new BufferedInputStream(configStream); + input = getSecurityPropertiesReader(); secprops.load(input); loaded = true; - configStream.close(); } catch (Exception ex) { System.logE("Could not load 'security.properties'", ex); + } finally { + if (input != null) { + try { + input.close(); + } catch (Exception ex) { + System.logW("Could not close 'security.properties'", ex); + } + } } if (!loaded) { registerDefaultProviders(); @@ -64,6 +75,11 @@ public final class Security { Engine.door = new SecurityDoor(); } + private static Reader getSecurityPropertiesReader() throws Exception { + InputStream configStream = Security.class.getResourceAsStream("security.properties"); + return new InputStreamReader(new BufferedInputStream(configStream), "ISO-8859-1"); + } + /** * This class can't be instantiated. */ diff --git a/luni/src/main/java/java/security/Signature.java b/luni/src/main/java/java/security/Signature.java index a39d59b..3151058 100644 --- a/luni/src/main/java/java/security/Signature.java +++ b/luni/src/main/java/java/security/Signature.java @@ -193,7 +193,7 @@ public abstract class Signature extends SignatureSpi { if (service == null) { return null; } - return tryAlgorithmWithProvider(key, service); + return tryAlgorithmWithProvider(null, service); } ArrayList<Provider.Service> services = ENGINE.getServices(algorithm); if (services == null) { @@ -245,6 +245,15 @@ public abstract class Signature extends SignatureSpi { } /** + * Gets the SPI implementation backing this signature. + * + * @hide + */ + public SignatureSpi getSpi() { + return null; + } + + /** * Returns the name of the algorithm of this {@code Signature}. * * @return the name of the algorithm of this {@code Signature}. @@ -730,8 +739,11 @@ public abstract class Signature extends SignatureSpi { /** * Convenience call when the Key is not available. + * + * @hide */ - private SignatureSpi getSpi() { + @Override + public SignatureSpi getSpi() { return getSpi(null); } } diff --git a/luni/src/main/java/java/text/BreakIterator.java b/luni/src/main/java/java/text/BreakIterator.java index 81545b2..051ea15 100644 --- a/luni/src/main/java/java/text/BreakIterator.java +++ b/luni/src/main/java/java/text/BreakIterator.java @@ -18,8 +18,6 @@ package java.text; import java.util.Locale; -import libcore.icu.ICU; -import libcore.icu.NativeBreakIterator; /** * Locates boundaries in text. This class defines a protocol for objects that @@ -230,29 +228,19 @@ public abstract class BreakIterator implements Cloneable { */ public static final int DONE = -1; - // the wrapped ICU implementation - NativeBreakIterator wrapped; - /** * Default constructor, for use by subclasses. */ protected BreakIterator() { } - /* - * wrapping constructor - */ - BreakIterator(NativeBreakIterator iterator) { - wrapped = iterator; - } - /** * Returns an array of locales for which custom {@code BreakIterator} instances * are available. * <p>Note that Android does not support user-supplied locale service providers. */ public static Locale[] getAvailableLocales() { - return ICU.getAvailableBreakIteratorLocales(); + return com.ibm.icu.text.BreakIterator.getAvailableLocales(); } /** @@ -270,7 +258,8 @@ public abstract class BreakIterator implements Cloneable { * characters using the given locale. */ public static BreakIterator getCharacterInstance(Locale locale) { - return new RuleBasedBreakIterator(NativeBreakIterator.getCharacterInstance(locale)); + return new IcuIteratorWrapper( + com.ibm.icu.text.BreakIterator.getCharacterInstance(locale)); } /** @@ -288,7 +277,8 @@ public abstract class BreakIterator implements Cloneable { * line breaks using the given locale. */ public static BreakIterator getLineInstance(Locale locale) { - return new RuleBasedBreakIterator(NativeBreakIterator.getLineInstance(locale)); + return new IcuIteratorWrapper( + com.ibm.icu.text.BreakIterator.getLineInstance(locale)); } /** @@ -306,7 +296,8 @@ public abstract class BreakIterator implements Cloneable { * sentence-breaks using the given locale. */ public static BreakIterator getSentenceInstance(Locale locale) { - return new RuleBasedBreakIterator(NativeBreakIterator.getSentenceInstance(locale)); + return new IcuIteratorWrapper( + com.ibm.icu.text.BreakIterator.getSentenceInstance(locale)); } /** @@ -324,7 +315,8 @@ public abstract class BreakIterator implements Cloneable { * word-breaks using the given locale. */ public static BreakIterator getWordInstance(Locale locale) { - return new RuleBasedBreakIterator(NativeBreakIterator.getWordInstance(locale)); + return new IcuIteratorWrapper( + com.ibm.icu.text.BreakIterator.getWordInstance(locale)); } /** @@ -339,7 +331,7 @@ public abstract class BreakIterator implements Cloneable { * false} otherwise. */ public boolean isBoundary(int offset) { - return wrapped.isBoundary(offset); + return false; } /** @@ -354,7 +346,7 @@ public abstract class BreakIterator implements Cloneable { * if the offset is invalid. */ public int preceding(int offset) { - return wrapped.preceding(offset); + return 0; } /** @@ -366,10 +358,6 @@ public abstract class BreakIterator implements Cloneable { * the new text string to be analyzed. */ public void setText(String newText) { - if (newText == null) { - throw new NullPointerException("newText == null"); - } - wrapped.setText(newText); } /** @@ -466,9 +454,7 @@ public abstract class BreakIterator implements Cloneable { @Override public Object clone() { try { - BreakIterator cloned = (BreakIterator) super.clone(); - cloned.wrapped = (NativeBreakIterator) wrapped.clone(); - return cloned; + return (BreakIterator) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(e); } diff --git a/luni/src/main/java/java/text/ChoiceFormat.java b/luni/src/main/java/java/text/ChoiceFormat.java index 014b8c7..3d3cb3a 100644 --- a/luni/src/main/java/java/text/ChoiceFormat.java +++ b/luni/src/main/java/java/text/ChoiceFormat.java @@ -303,37 +303,16 @@ public class ChoiceFormat extends NumberFormat { } /** - * Returns the double value which is closest to the specified double but - * larger. - * - * @param value - * a double value. - * @return the next larger double value. + * Equivalent to {@link Math#nextUp(double)}. */ public static final double nextDouble(double value) { - if (value == Double.POSITIVE_INFINITY) { - return value; - } - long bits; - // Handle -0.0 - if (value == 0) { - bits = 0; - } else { - bits = Double.doubleToLongBits(value); - } - return Double.longBitsToDouble(value < 0 ? bits - 1 : bits + 1); + return Math.nextUp(value); } /** - * Returns the double value which is closest to the specified double but - * either larger or smaller as specified. - * - * @param value - * a double value. - * @param increment - * {@code true} to get the next larger value, {@code false} to - * get the previous smaller value. - * @return the next larger or smaller double value. + * Equivalent to {@link Math#nextUp(double)} if {@code increment == true}, and + * {@link Math#nextAfter(double, double)} with {@code direction == Double.NEGATIVE_INFINITY} + * otherwise. */ public static double nextDouble(double value, boolean increment) { return increment ? nextDouble(value) : previousDouble(value); @@ -385,25 +364,11 @@ public class ChoiceFormat extends NumberFormat { } /** - * Returns the double value which is closest to the specified double but - * smaller. - * - * @param value - * a double value. - * @return the next smaller double value. + * Equivalent to {@link Math#nextAfter(double, double)} with + * {@code direction == Double.NEGATIVE_INFINITY}. */ public static final double previousDouble(double value) { - if (value == Double.NEGATIVE_INFINITY) { - return value; - } - long bits; - // Handle 0.0 - if (value == 0) { - bits = 0x8000000000000000L; - } else { - bits = Double.doubleToLongBits(value); - } - return Double.longBitsToDouble(value <= 0 ? bits + 1 : bits - 1); + return Math.nextAfter(value, Double.NEGATIVE_INFINITY); } /** @@ -453,9 +418,30 @@ public class ChoiceFormat extends NumberFormat { if (i != 0) { buffer.append('|'); } - String previous = String.valueOf(previousDouble(choiceLimits[i])); - String limit = String.valueOf(choiceLimits[i]); - if (previous.length() < limit.length()) { + + final String previous = String.valueOf(previousDouble(choiceLimits[i])); + final String limit = String.valueOf(choiceLimits[i]); + + // Hack to make the output of toPattern parseable by another ChoiceFormat. + // String.valueOf() will emit "Infinity", which isn't parseable by our NumberFormat + // instances. + // + // Ideally, we'd just use NumberFormat.format() to emit output (to be symmetric with + // our usage of NumberFormat.parse()) but it's hard set the right number of significant + // digits in order to output a format string that's equivalent to the original input. + if (Double.isInfinite(choiceLimits[i]) || + Double.isInfinite(previousDouble(choiceLimits[i]))) { + if (choiceLimits[i] < 0) { + buffer.append("-\u221E"); + buffer.append('<'); + } else { + buffer.append('\u221E'); + buffer.append('<'); + } + } else if (previous.length() < limit.length()) { + // What the... i don't even.... sigh. This is trying to figure out whether the + // element was a "<" or a "#". The idea being that users will specify "reasonable" + // quantities and calling nextDouble will result in a "longer" number in most cases. buffer.append(previous); buffer.append('<'); } else { diff --git a/luni/src/main/java/java/text/Format.java b/luni/src/main/java/java/text/Format.java index 58671fa..c4dc5f0 100644 --- a/luni/src/main/java/java/text/Format.java +++ b/luni/src/main/java/java/text/Format.java @@ -177,23 +177,26 @@ public abstract class Format implements Serializable, Cloneable { static boolean upTo(String string, ParsePosition position, StringBuffer buffer, char stop) { int index = position.getIndex(), length = string.length(); - boolean lastQuote = false, quote = false; + + int numConsecutiveQuotes = 0; + boolean quote = false; while (index < length) { char ch = string.charAt(index++); if (ch == '\'') { - if (lastQuote) { + ++numConsecutiveQuotes; + if (numConsecutiveQuotes != 0 && numConsecutiveQuotes % 2 == 0) { buffer.append('\''); } quote = !quote; - lastQuote = true; } else if (ch == stop && !quote) { position.setIndex(index); return true; } else { - lastQuote = false; + numConsecutiveQuotes = 0; buffer.append(ch); } } + position.setIndex(index); return false; } diff --git a/luni/src/main/java/java/text/RuleBasedBreakIterator.java b/luni/src/main/java/java/text/IcuIteratorWrapper.java index a16968e..b863fa7 100644 --- a/luni/src/main/java/java/text/RuleBasedBreakIterator.java +++ b/luni/src/main/java/java/text/IcuIteratorWrapper.java @@ -17,7 +17,6 @@ package java.text; -import libcore.icu.NativeBreakIterator; /* * Default implementation of BreakIterator. Wraps libcore.icu.NativeBreakIterator. @@ -25,10 +24,13 @@ import libcore.icu.NativeBreakIterator; * and we don't have Java implementations of those methods (other than the current ones, which * forward to the wrapped NativeBreakIterator). */ -class RuleBasedBreakIterator extends BreakIterator { +class IcuIteratorWrapper extends BreakIterator { - RuleBasedBreakIterator(NativeBreakIterator iterator) { - super(iterator); + /* The wrapped ICU implementation. Non-final for #clone() */ + private com.ibm.icu.text.BreakIterator wrapped; + + IcuIteratorWrapper(com.ibm.icu.text.BreakIterator iterator) { + wrapped = iterator; } @Override public int current() { @@ -44,17 +46,6 @@ class RuleBasedBreakIterator extends BreakIterator { return wrapped.following(offset); } - private void checkOffset(int offset) { - if (!wrapped.hasText()) { - throw new IllegalArgumentException("BreakIterator has no text"); - } - CharacterIterator it = wrapped.getText(); - if (offset < it.getBeginIndex() || offset > it.getEndIndex()) { - String message = "Valid range is [" + it.getBeginIndex() + " " + it.getEndIndex() + "]"; - throw new IllegalArgumentException(message); - } - } - @Override public CharacterIterator getText() { return wrapped.getText(); } @@ -75,10 +66,11 @@ class RuleBasedBreakIterator extends BreakIterator { return wrapped.previous(); } + @Override public void setText(String newText) { + wrapped.setText(newText); + } + @Override public void setText(CharacterIterator newText) { - if (newText == null) { - throw new NullPointerException("newText == null"); - } newText.current(); wrapped.setText(newText); } @@ -94,10 +86,10 @@ class RuleBasedBreakIterator extends BreakIterator { } @Override public boolean equals(Object o) { - if (!(o instanceof RuleBasedBreakIterator)) { + if (!(o instanceof IcuIteratorWrapper)) { return false; } - return wrapped.equals(((RuleBasedBreakIterator) o).wrapped); + return wrapped.equals(((IcuIteratorWrapper) o).wrapped); } @Override public String toString() { @@ -108,9 +100,20 @@ class RuleBasedBreakIterator extends BreakIterator { return wrapped.hashCode(); } + private void checkOffset(int offset) { + final CharacterIterator it = wrapped.getText(); + if (it == null) { + throw new IllegalArgumentException("BreakIterator has no text"); + } + if (offset < it.getBeginIndex() || offset > it.getEndIndex()) { + String message = "Valid range is [" + it.getBeginIndex() + " " + it.getEndIndex() + "]"; + throw new IllegalArgumentException(message); + } + } + @Override public Object clone() { - RuleBasedBreakIterator cloned = (RuleBasedBreakIterator) super.clone(); - cloned.wrapped = (NativeBreakIterator) wrapped.clone(); + IcuIteratorWrapper cloned = (IcuIteratorWrapper) super.clone(); + cloned.wrapped = (com.ibm.icu.text.BreakIterator) wrapped.clone(); return cloned; } } diff --git a/luni/src/main/java/java/text/MessageFormat.java b/luni/src/main/java/java/text/MessageFormat.java index cf306a7..f48cebd 100644 --- a/luni/src/main/java/java/text/MessageFormat.java +++ b/luni/src/main/java/java/text/MessageFormat.java @@ -580,6 +580,12 @@ public class MessageFormat extends Format { } if (format instanceof ChoiceFormat) { String result = format.format(arg); + // Escape quotes in the result because the ChoiceFormat would've already + // dealt with them for us. In other words, any quotes that are present in the + // result are due to escaped quotes in the original input. We should preserve + // them in the output instead of having them processed again with the message + // format we're creating below. + result = result.replace("'", "''"); MessageFormat mf = new MessageFormat(result); mf.setLocale(locale); mf.format(objects, buffer, passedField); diff --git a/luni/src/main/java/java/text/NumberFormat.java b/luni/src/main/java/java/text/NumberFormat.java index a1f10d4..2526ffc 100644 --- a/luni/src/main/java/java/text/NumberFormat.java +++ b/luni/src/main/java/java/text/NumberFormat.java @@ -492,6 +492,10 @@ public abstract class NumberFormat extends Format { * to format floating-point numbers typically between 0 and 1 (with 1 being 100%). * A value such as 0.53 will be treated as 53%, but 53.0 (or the integer 53) will be * treated as 5,300%, which is rarely what you intended. + * + * <p>Non-integer percentages will be rounded according to the rounding mode, + * so by default 0.142 will be 14% but 0.148 will be 15%. If you want fractional + * percentages, use {@link #setMaximumFractionDigits}. */ public static final NumberFormat getPercentInstance() { return getPercentInstance(Locale.getDefault()); @@ -505,6 +509,10 @@ public abstract class NumberFormat extends Format { * to format floating-point numbers typically between 0 and 1 (with 1 being 100%). * A value such as 0.53 will be treated as 53%, but 53.0 (or the integer 53) will be * treated as 5,300%, which is rarely what you intended. + * + * <p>Non-integer percentages will be rounded according to the rounding mode, + * so by default 0.142 will be 14% but 0.148 will be 15%. If you want fractional + * percentages, use {@link #setMaximumFractionDigits}. */ public static NumberFormat getPercentInstance(Locale locale) { if (locale == null) { diff --git a/luni/src/main/java/java/text/SimpleDateFormat.java b/luni/src/main/java/java/text/SimpleDateFormat.java index 8f83ff7..4e7a950 100644 --- a/luni/src/main/java/java/text/SimpleDateFormat.java +++ b/luni/src/main/java/java/text/SimpleDateFormat.java @@ -91,7 +91,16 @@ import libcore.icu.TimeZoneNames; * <tr> <td>{@code ''}</td> <td>single quote</td> <td>(Literal)</td> <td>{@code 'o''clock'}:o'clock</td> </tr> * </table> * - * <p>Fractional seconds are handled specially: they're zero-padded on the <i>right</i>. + * <p>Note that {@code 'S'} represents fractional seconds and not millisecond values. + * They will be padded on the left or on the right or both depending on the number of + * {@code 'S'} in the pattern. For example, the number of fractional seconds in a + * {@code Date} where {@code Date.getTime() == 1000006} are {@code 0.006} or + * {@code (6 / 1000)}. This leads to the following formatting: + * <ul> + * <li> {@code "S" => "0"} </li> + * <li> {@code "SSS" => "006" } </li> + * <li> {@code "SSSSSS" => "006000" }</li> + * </ul> * * <p>The two pattern characters {@code L} and {@code c} are ICU-compatible extensions, not * available in the RI or in Android before Android 2.3 (Gingerbread, API level 9). These diff --git a/luni/src/main/java/java/util/Calendar.java b/luni/src/main/java/java/util/Calendar.java index fc4cef6..6057547 100644 --- a/luni/src/main/java/java/util/Calendar.java +++ b/luni/src/main/java/java/util/Calendar.java @@ -810,7 +810,9 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca } /** - * Returns a shallow copy of this {@code Calendar} with the same properties. + * Returns a partially deep copy of this {@code Calendar}; all fields from + * from the {@code Calendar} class are cloned (deep copy) but fields from + * subclasses aren't (shallow copy). */ @Override public Object clone() { diff --git a/luni/src/main/java/java/util/Collections.java b/luni/src/main/java/java/util/Collections.java index 4541d64..dc4161f 100644 --- a/luni/src/main/java/java/util/Collections.java +++ b/luni/src/main/java/java/util/Collections.java @@ -102,6 +102,10 @@ public class Collections { throw new IndexOutOfBoundsException(); } + @Override public Iterator iterator() { + return EMPTY_ITERATOR; + } + private Object readResolve() { return Collections.EMPTY_LIST; } @@ -1422,21 +1426,21 @@ public class Collections { if (!(list instanceof RandomAccess)) { ListIterator<? extends Comparable<? super T>> it = list.listIterator(); while (it.hasNext()) { - int result; - if ((result = -it.next().compareTo(object)) <= 0) { - if (result == 0) { - return it.previousIndex(); - } + final int result = it.next().compareTo(object); + if (result == 0) { + return it.previousIndex(); + } else if (result > 0) { return -it.previousIndex() - 1; } } return -list.size() - 1; } - int low = 0, mid = list.size(), high = mid - 1, result = -1; + int low = 0, mid = list.size(), high = mid - 1, result = 1; while (low <= high) { mid = (low + high) >>> 1; - if ((result = -list.get(mid).compareTo(object)) > 0) { + result = list.get(mid).compareTo(object); + if (result < 0) { low = mid + 1; } else if (result == 0) { return mid; @@ -1444,7 +1448,7 @@ public class Collections { high = mid - 1; } } - return -mid - (result < 0 ? 1 : 2); + return -mid - (result > 0 ? 1 : 2); } /** @@ -1477,21 +1481,21 @@ public class Collections { if (!(list instanceof RandomAccess)) { ListIterator<? extends T> it = list.listIterator(); while (it.hasNext()) { - int result; - if ((result = -comparator.compare(it.next(), object)) <= 0) { - if (result == 0) { - return it.previousIndex(); - } + final int result = comparator.compare(it.next(), object); + if (result == 0) { + return it.previousIndex(); + } else if (result > 0) { return -it.previousIndex() - 1; } } return -list.size() - 1; } - int low = 0, mid = list.size(), high = mid - 1, result = -1; + int low = 0, mid = list.size(), high = mid - 1, result = 1; while (low <= high) { mid = (low + high) >>> 1; - if ((result = -comparator.compare(list.get(mid), object)) > 0) { + result = comparator.compare(list.get(mid), object); + if (result < 0) { low = mid + 1; } else if (result == 0) { return mid; @@ -1499,7 +1503,7 @@ public class Collections { high = mid - 1; } } - return -mid - (result < 0 ? 1 : 2); + return -mid - (result > 0 ? 1 : 2); } /** @@ -1860,13 +1864,23 @@ public class Collections { */ @SuppressWarnings("unchecked") public static <T extends Comparable<? super T>> void sort(List<T> list) { - Object[] array = list.toArray(); - Arrays.sort(array); - int i = 0; - ListIterator<T> it = list.listIterator(); - while (it.hasNext()) { - it.next(); - it.set((T) array[i++]); + // Note that we can't use instanceof here since ArrayList isn't final and + // subclasses might make arbitrary use of array and modCount. + if (list.getClass() == ArrayList.class) { + ArrayList<T> arrayList = (ArrayList<T>) list; + Object[] array = arrayList.array; + int end = arrayList.size(); + Arrays.sort(array, 0, end); + arrayList.modCount++; + } else { + Object[] array = list.toArray(); + Arrays.sort(array); + int i = 0; + ListIterator<T> it = list.listIterator(); + while (it.hasNext()) { + it.next(); + it.set((T) array[i++]); + } } } @@ -1879,13 +1893,21 @@ public class Collections { */ @SuppressWarnings("unchecked") public static <T> void sort(List<T> list, Comparator<? super T> comparator) { - T[] array = list.toArray((T[]) new Object[list.size()]); - Arrays.sort(array, comparator); - int i = 0; - ListIterator<T> it = list.listIterator(); - while (it.hasNext()) { - it.next(); - it.set(array[i++]); + if (list.getClass() == ArrayList.class) { + ArrayList<T> arrayList = (ArrayList<T>) list; + T[] array = (T[]) arrayList.array; + int end = arrayList.size(); + Arrays.sort(array, 0, end, comparator); + arrayList.modCount++; + } else { + T[] array = list.toArray((T[]) new Object[list.size()]); + Arrays.sort(array, comparator); + int i = 0; + ListIterator<T> it = list.listIterator(); + while (it.hasNext()) { + it.next(); + it.set(array[i++]); + } } } diff --git a/luni/src/main/java/java/util/Date.java b/luni/src/main/java/java/util/Date.java index b4de055..d45c971 100644 --- a/luni/src/main/java/java/util/Date.java +++ b/luni/src/main/java/java/util/Date.java @@ -47,7 +47,10 @@ public class Date implements Serializable, Cloneable, Comparable<Date> { private static final long serialVersionUID = 7523967970034938905L; // Used by parse() - private static final int CREATION_YEAR = new Date().getYear(); + // Keep in a static inner class to allow compile-time initialization of Date. + private static class CreationYear { + private static final int VALUE = new Date().getYear(); + } private transient long milliseconds; @@ -539,7 +542,7 @@ public class Date implements Serializable, Cloneable, Comparable<Date> { if (second == -1) { second = 0; } - if (year < (CREATION_YEAR - 80)) { + if (year < (CreationYear.VALUE - 80)) { year += 2000; } else if (year < 100) { year += 1900; diff --git a/luni/src/main/java/java/util/EnumMap.java b/luni/src/main/java/java/util/EnumMap.java index dfacb46..eee6ff4 100644 --- a/luni/src/main/java/java/util/EnumMap.java +++ b/luni/src/main/java/java/util/EnumMap.java @@ -36,9 +36,9 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements private Class<K> keyType; - transient Enum[] keys; + transient K[] keys; - transient Object[] values; + transient V[] values; transient boolean[] hasMapping; @@ -48,8 +48,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements private transient EnumMapEntrySet<K, V> entrySet = null; - private static class Entry<KT extends Enum<KT>, VT> extends - MapEntry<KT, VT> { + private static class Entry<KT extends Enum<KT>, VT> extends MapEntry<KT, VT> { private final EnumMap<KT, VT> enumMap; private final int ordinal; @@ -57,10 +56,9 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements Entry(KT theKey, VT theValue, EnumMap<KT, VT> em) { super(theKey, theValue); enumMap = em; - ordinal = ((Enum) theKey).ordinal(); + ordinal = theKey.ordinal(); } - @SuppressWarnings("unchecked") @Override public boolean equals(Object object) { if (!enumMap.hasMapping[ordinal]) { @@ -68,7 +66,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements } boolean isEqual = false; if (object instanceof Map.Entry) { - Map.Entry<KT, VT> entry = (Map.Entry<KT, VT>) object; + Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object; Object enumKey = entry.getKey(); if (key.equals(enumKey)) { Object theValue = entry.getValue(); @@ -84,37 +82,32 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements @Override public int hashCode() { - return (enumMap.keys[ordinal] == null ? 0 : enumMap.keys[ordinal] - .hashCode()) + return (enumMap.keys[ordinal] == null ? 0 : enumMap.keys[ordinal].hashCode()) ^ (enumMap.values[ordinal] == null ? 0 : enumMap.values[ordinal].hashCode()); } - @SuppressWarnings("unchecked") @Override public KT getKey() { checkEntryStatus(); - return (KT) enumMap.keys[ordinal]; + return enumMap.keys[ordinal]; } - @SuppressWarnings("unchecked") @Override public VT getValue() { checkEntryStatus(); - return (VT) enumMap.values[ordinal]; + return enumMap.values[ordinal]; } - @SuppressWarnings("unchecked") @Override public VT setValue(VT value) { checkEntryStatus(); - return enumMap.put((KT) enumMap.keys[ordinal], value); + return enumMap.put(enumMap.keys[ordinal], value); } @Override public String toString() { - StringBuilder result = new StringBuilder(enumMap.keys[ordinal] - .toString()); + StringBuilder result = new StringBuilder(enumMap.keys[ordinal].toString()); result.append("="); result.append(enumMap.values[ordinal] == null ? "null" : enumMap.values[ordinal].toString()); @@ -128,8 +121,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements } } - private static class EnumMapIterator<E, KT extends Enum<KT>, VT> implements - Iterator<E> { + private static class EnumMapIterator<E, KT extends Enum<KT>, VT> implements Iterator<E> { int position = 0; int prePosition = -1; @@ -153,13 +145,12 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements return position != length; } - @SuppressWarnings("unchecked") public E next() { if (!hasNext()) { throw new NoSuchElementException(); } prePosition = position++; - return type.get(new MapEntry(enumMap.keys[prePosition], + return type.get(new MapEntry<KT, VT>(enumMap.keys[prePosition], enumMap.values[prePosition])); } @@ -172,13 +163,12 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements } @Override - @SuppressWarnings("unchecked") public String toString() { if (prePosition == -1) { return super.toString(); } return type.get( - new MapEntry(enumMap.keys[prePosition], + new MapEntry<KT, VT>(enumMap.keys[prePosition], enumMap.values[prePosition])).toString(); } @@ -208,8 +198,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements } @Override - @SuppressWarnings("unchecked") - public Iterator iterator() { + public Iterator<KT> iterator() { return new EnumMapIterator<KT, KT, VT>( new MapEntry.Type<KT, KT, VT>() { public KT get(MapEntry<KT, VT> entry) { @@ -219,7 +208,6 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements } @Override - @SuppressWarnings("unchecked") public boolean remove(Object object) { if (contains(object)) { enumMap.remove(object); @@ -252,9 +240,8 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements return enumMap.containsValue(object); } - @SuppressWarnings("unchecked") @Override - public Iterator iterator() { + public Iterator<VT> iterator() { return new EnumMapIterator<VT, KT, VT>( new MapEntry.Type<VT, KT, VT>() { public VT get(MapEntry<KT, VT> entry) { @@ -296,15 +283,14 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements super(value, em); } - @SuppressWarnings("unchecked") @Override public E next() { if (!hasNext()) { throw new NoSuchElementException(); } prePosition = position++; - return type.get(new Entry<KT, VT>((KT) enumMap.keys[prePosition], - (VT) enumMap.values[prePosition], enumMap)); + return type.get(new EnumMap.Entry<KT, VT>(enumMap.keys[prePosition], + enumMap.values[prePosition], enumMap)); } } @@ -325,8 +311,8 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements public boolean contains(Object object) { boolean isEqual = false; if (object instanceof Map.Entry) { - Object enumKey = ((Map.Entry) object).getKey(); - Object enumValue = ((Map.Entry) object).getValue(); + Object enumKey = ((Map.Entry<?, ?>) object).getKey(); + Object enumValue = ((Map.Entry<?, ?>) object).getValue(); if (enumMap.containsKey(enumKey)) { VT value = enumMap.get(enumKey); if (value == null) { @@ -352,7 +338,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements @Override public boolean remove(Object object) { if (contains(object)) { - enumMap.remove(((Map.Entry) object).getKey()); + enumMap.remove(((Map.Entry<?, ?>) object).getKey()); return true; } return false; @@ -369,21 +355,22 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements return toArray(entryArray); } - @SuppressWarnings("unchecked") @Override - public Object[] toArray(Object[] array) { + public <T> T[] toArray(T[] array) { int size = enumMap.size(); int index = 0; - Object[] entryArray = array; + T[] entryArray = array; if (size > array.length) { Class<?> clazz = array.getClass().getComponentType(); - entryArray = (Object[]) Array.newInstance(clazz, size); + @SuppressWarnings("unchecked") T[] newArray = (T[]) Array.newInstance(clazz, size); + entryArray = newArray; } Iterator<Map.Entry<KT, VT>> iter = iterator(); for (; index < size; index++) { Map.Entry<KT, VT> entry = iter.next(); - entryArray[index] = new MapEntry<KT, VT>(entry.getKey(), entry - .getValue()); + @SuppressWarnings("unchecked") T newEntry = + (T) new MapEntry<KT, VT>(entry.getKey(), entry.getValue()); + entryArray[index] = newEntry; } if (index < array.length) { entryArray[index] = null; @@ -431,22 +418,27 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements * @throws NullPointerException * if {@code map} is {@code null}. */ - @SuppressWarnings("unchecked") public EnumMap(Map<K, ? extends V> map) { if (map instanceof EnumMap) { - initialization((EnumMap<K, V>) map); + @SuppressWarnings("unchecked") EnumMap<K, ? extends V> enumMap = + (EnumMap<K, ? extends V>) map; + initialization(enumMap); } else { if (map.isEmpty()) { throw new IllegalArgumentException("map is empty"); } Iterator<K> iter = map.keySet().iterator(); K enumKey = iter.next(); - Class clazz = enumKey.getClass(); - if (clazz.isEnum()) { - initialization(clazz); - } else { - initialization(clazz.getSuperclass()); + // Confirm the key is actually an enum: Throw ClassCastException if not. + Enum.class.cast(enumKey); + Class<?> clazz = enumKey.getClass(); + if (!clazz.isEnum()) { + // Each enum value can have its own subclass. In this case we want the abstract + // super-class which has the values() method. + clazz = clazz.getSuperclass(); } + @SuppressWarnings("unchecked") Class<K> enumClass = (Class<K>) clazz; + initialization(enumClass); putAllImpl(map); } } @@ -469,11 +461,10 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements * * @return a shallow copy of this {@code EnumMap}. */ - @SuppressWarnings("unchecked") @Override public EnumMap<K, V> clone() { try { - EnumMap<K, V> enumMap = (EnumMap<K, V>) super.clone(); + @SuppressWarnings("unchecked") EnumMap<K, V> enumMap = (EnumMap<K, V>) super.clone(); enumMap.initialization(this); return enumMap; } catch (CloneNotSupportedException e) { @@ -553,7 +544,6 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements * @see #hashCode() * @see #entrySet() */ - @SuppressWarnings("unchecked") @Override public boolean equals(Object object) { if (this == object) { @@ -562,7 +552,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements if (!(object instanceof EnumMap)) { return super.equals(object); } - EnumMap<K, V> enumMap = (EnumMap<K, V>) object; + @SuppressWarnings("unchecked") EnumMap<K, V> enumMap = (EnumMap<K, V>) object; if (keyType != enumMap.keyType || size() != enumMap.size()) { return false; } @@ -579,13 +569,12 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements * if no mapping for the specified key is found. */ @Override - @SuppressWarnings("unchecked") public V get(Object key) { if (!isValidKeyType(key)) { return null; } int keyOrdinal = ((Enum) key).ordinal(); - return (V) values[keyOrdinal]; + return values[keyOrdinal]; } /** @@ -627,7 +616,6 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements * support {@code null} keys or values. */ @Override - @SuppressWarnings("unchecked") public V put(K key, V value) { return putImpl(key, value); } @@ -649,7 +637,6 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements * support {@code null} keys or values. */ @Override - @SuppressWarnings("unchecked") public void putAll(Map<? extends K, ? extends V> map) { putAllImpl(map); } @@ -665,7 +652,6 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements * if removing from this {@code EnumMap} is not supported. */ @Override - @SuppressWarnings("unchecked") public V remove(Object key) { if (!isValidKeyType(key)) { return null; @@ -675,7 +661,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements hasMapping[keyOrdinal] = false; mappingsCount--; } - V oldValue = (V) values[keyOrdinal]; + V oldValue = values[keyOrdinal]; values[keyOrdinal] = null; return oldValue; } @@ -716,35 +702,29 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements stream.defaultReadObject(); initialization(keyType); int elementCount = stream.readInt(); - Enum<K> enumKey; - Object value; + K enumKey; + V value; for (int i = elementCount; i > 0; i--) { - enumKey = (Enum<K>) stream.readObject(); - value = stream.readObject(); - putImpl((K) enumKey, (V) value); + enumKey = (K) stream.readObject(); + value = (V) stream.readObject(); + putImpl(enumKey, value); } } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeInt(mappingsCount); - Iterator<Map.Entry<K, V>> iterator = entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry<K, V> entry = iterator.next(); + for (Map.Entry<K, V> entry : entrySet()) { stream.writeObject(entry.getKey()); stream.writeObject(entry.getValue()); } } private boolean isValidKeyType(Object key) { - if (key != null && keyType.isInstance(key)) { - return true; - } - return false; + return key != null && keyType.isInstance(key); } - @SuppressWarnings("unchecked") - private void initialization(EnumMap enumMap) { + private void initialization(EnumMap<K, ? extends V> enumMap) { keyType = enumMap.keyType; keys = enumMap.keys; enumSize = enumMap.enumSize; @@ -757,20 +737,19 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements keyType = type; keys = Enum.getSharedConstants(keyType); enumSize = keys.length; - values = new Object[enumSize]; + // The value array is actually Object[] for speed of creation. It is treated as a V[] + // because it is safe to do so and eliminates unchecked warning suppression throughout. + @SuppressWarnings("unchecked") V[] valueArray = (V[]) new Object[enumSize]; + values = valueArray; hasMapping = new boolean[enumSize]; } - @SuppressWarnings("unchecked") - private void putAllImpl(Map map) { - Iterator iter = map.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry entry = (Map.Entry) iter.next(); - putImpl((K) entry.getKey(), (V) entry.getValue()); + private void putAllImpl(Map<? extends K, ? extends V> map) { + for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { + putImpl(entry.getKey(), entry.getValue()); } } - @SuppressWarnings("unchecked") private V putImpl(K key, V value) { if (key == null) { throw new NullPointerException("key == null"); @@ -781,7 +760,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements hasMapping[keyOrdinal] = true; mappingsCount++; } - V oldValue = (V) values[keyOrdinal]; + V oldValue = values[keyOrdinal]; values[keyOrdinal] = value; return oldValue; } diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java index a6368e8..1885f83 100644 --- a/luni/src/main/java/java/util/Locale.java +++ b/luni/src/main/java/java/util/Locale.java @@ -272,17 +272,77 @@ public final class Locale implements Cloneable, Serializable { */ private static final String UNDETERMINED_LANGUAGE = "und"; + /** - * The current default locale. It is temporarily assigned to US because we - * need a default locale to lookup the real default locale. + * Map of grandfathered language tags to their modern replacements. */ - private static Locale defaultLocale = US; + private static final TreeMap<String, String> GRANDFATHERED_LOCALES; static { - String language = System.getProperty("user.language", "en"); - String region = System.getProperty("user.region", "US"); - String variant = System.getProperty("user.variant", ""); - defaultLocale = new Locale(language, region, variant); + GRANDFATHERED_LOCALES = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); + + // From http://tools.ietf.org/html/bcp47 + // + // grandfathered = irregular ; non-redundant tags registered + // / regular ; during the RFC 3066 era + // irregular = + GRANDFATHERED_LOCALES.put("en-GB-oed", "en-GB-x-oed"); + GRANDFATHERED_LOCALES.put("i-ami", "ami"); + GRANDFATHERED_LOCALES.put("i-bnn", "bnn"); + GRANDFATHERED_LOCALES.put("i-default", "en-x-i-default"); + GRANDFATHERED_LOCALES.put("i-enochian", "und-x-i-enochian"); + GRANDFATHERED_LOCALES.put("i-hak", "hak"); + GRANDFATHERED_LOCALES.put("i-klingon", "tlh"); + GRANDFATHERED_LOCALES.put("i-lux", "lb"); + GRANDFATHERED_LOCALES.put("i-mingo", "see-x-i-mingo"); + GRANDFATHERED_LOCALES.put("i-navajo", "nv"); + GRANDFATHERED_LOCALES.put("i-pwn", "pwn"); + GRANDFATHERED_LOCALES.put("i-tao", "tao"); + GRANDFATHERED_LOCALES.put("i-tay", "tay"); + GRANDFATHERED_LOCALES.put("i-tsu", "tsu"); + GRANDFATHERED_LOCALES.put("sgn-BE-FR", "sfb"); + GRANDFATHERED_LOCALES.put("sgn-BE-NL", "vgt"); + GRANDFATHERED_LOCALES.put("sgn-CH-DE", "sgg"); + + // regular = + GRANDFATHERED_LOCALES.put("art-lojban", "jbo"); + GRANDFATHERED_LOCALES.put("cel-gaulish", "xtg-x-cel-gaulish"); + GRANDFATHERED_LOCALES.put("no-bok", "nb"); + GRANDFATHERED_LOCALES.put("no-nyn", "nn"); + GRANDFATHERED_LOCALES.put("zh-guoyu", "cmn"); + GRANDFATHERED_LOCALES.put("zh-hakka", "hak"); + GRANDFATHERED_LOCALES.put("zh-min", "nan-x-zh-min"); + GRANDFATHERED_LOCALES.put("zh-min-nan", "nan"); + GRANDFATHERED_LOCALES.put("zh-xiang", "hsn"); + } + + private static class NoImagePreloadHolder { + /** + * The default locale, returned by {@code Locale.getDefault()}. + * Initialize the default locale from the system properties. + */ + private static Locale defaultLocale = Locale.getDefaultLocaleFromSystemProperties(); + } + + /** + * Returns the default locale from system properties. + * + * @hide visible for testing. + */ + public static Locale getDefaultLocaleFromSystemProperties() { + final String languageTag = System.getProperty("user.locale", ""); + + final Locale defaultLocale; + if (!languageTag.isEmpty()) { + defaultLocale = Locale.forLanguageTag(languageTag); + } else { + String language = System.getProperty("user.language", "en"); + String region = System.getProperty("user.region", "US"); + String variant = System.getProperty("user.variant", ""); + defaultLocale = new Locale(language, region, variant); + } + + return defaultLocale; } /** @@ -1006,7 +1066,7 @@ public final class Locale implements Cloneable, Serializable { * Instead, use this method to look it up for each use. */ public static Locale getDefault() { - return defaultLocale; + return NoImagePreloadHolder.defaultLocale; } /** @@ -1600,7 +1660,7 @@ public final class Locale implements Cloneable, Serializable { throw new NullPointerException("locale == null"); } String languageTag = locale.toLanguageTag(); - defaultLocale = locale; + NoImagePreloadHolder.defaultLocale = locale; ICU.setDefaultLocale(languageTag); } @@ -2020,49 +2080,6 @@ public final class Locale implements Cloneable, Serializable { return adjusted; } - /** - * Map of grandfathered language tags to their modern replacements. - */ - private static final TreeMap<String, String> GRANDFATHERED_LOCALES; - - static { - GRANDFATHERED_LOCALES = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); - - // From http://tools.ietf.org/html/bcp47 - // - // grandfathered = irregular ; non-redundant tags registered - // / regular ; during the RFC 3066 era - // irregular = - GRANDFATHERED_LOCALES.put("en-GB-oed", "en-GB-x-oed"); - GRANDFATHERED_LOCALES.put("i-ami", "ami"); - GRANDFATHERED_LOCALES.put("i-bnn", "bnn"); - GRANDFATHERED_LOCALES.put("i-default", "en-x-i-default"); - GRANDFATHERED_LOCALES.put("i-enochian", "und-x-i-enochian"); - GRANDFATHERED_LOCALES.put("i-hak", "hak"); - GRANDFATHERED_LOCALES.put("i-klingon", "tlh"); - GRANDFATHERED_LOCALES.put("i-lux", "lb"); - GRANDFATHERED_LOCALES.put("i-mingo", "see-x-i-mingo"); - GRANDFATHERED_LOCALES.put("i-navajo", "nv"); - GRANDFATHERED_LOCALES.put("i-pwn", "pwn"); - GRANDFATHERED_LOCALES.put("i-tao", "tao"); - GRANDFATHERED_LOCALES.put("i-tay", "tay"); - GRANDFATHERED_LOCALES.put("i-tsu", "tsu"); - GRANDFATHERED_LOCALES.put("sgn-BE-FR", "sfb"); - GRANDFATHERED_LOCALES.put("sgn-BE-NL", "vgt"); - GRANDFATHERED_LOCALES.put("sgn-CH-DE", "sgg"); - - // regular = - GRANDFATHERED_LOCALES.put("art-lojban", "jbo"); - GRANDFATHERED_LOCALES.put("cel-gaulish", "xtg-x-cel-gaulish"); - GRANDFATHERED_LOCALES.put("no-bok", "nb"); - GRANDFATHERED_LOCALES.put("no-nyn", "nn"); - GRANDFATHERED_LOCALES.put("zh-guoyu", "cmn"); - GRANDFATHERED_LOCALES.put("zh-hakka", "hak"); - GRANDFATHERED_LOCALES.put("zh-min", "nan-x-zh-min"); - GRANDFATHERED_LOCALES.put("zh-min-nan", "nan"); - GRANDFATHERED_LOCALES.put("zh-xiang", "hsn"); - } - private static String convertGrandfatheredTag(String original) { final String converted = GRANDFATHERED_LOCALES.get(original); return converted != null ? converted : original; diff --git a/luni/src/main/java/java/util/TimeZone.java b/luni/src/main/java/java/util/TimeZone.java index 854a4a6..d7beb91 100644 --- a/luni/src/main/java/java/util/TimeZone.java +++ b/luni/src/main/java/java/util/TimeZone.java @@ -68,7 +68,51 @@ import org.apache.harmony.luni.internal.util.TimezoneGetter; public abstract class TimeZone implements Serializable, Cloneable { private static final long serialVersionUID = 3581463369166924961L; - private static final Pattern CUSTOM_ZONE_ID_PATTERN = Pattern.compile("^GMT[-+](\\d{1,2})(:?(\\d\\d))?$"); + /** + * Helper class to parse a custom timezone. This is in a separate class as regular expressions + * cannot be compile-time initialized, so that static field is separated out from TimeZone + * proper. + */ + private final static class CustomTimeZoneParser { + private static final Pattern CUSTOM_ZONE_ID_PATTERN = + Pattern.compile("^GMT[-+](\\d{1,2})(:?(\\d\\d))?$"); + + private CustomTimeZoneParser() {} + + /** + * Returns a new SimpleTimeZone for an ID of the form "GMT[+|-]hh[[:]mm]", or null. + */ + private static TimeZone getCustomTimeZone(String id) { + Matcher m = CUSTOM_ZONE_ID_PATTERN.matcher(id); + if (!m.matches()) { + return null; + } + + int hour; + int minute = 0; + try { + hour = Integer.parseInt(m.group(1)); + if (m.group(3) != null) { + minute = Integer.parseInt(m.group(3)); + } + } catch (NumberFormatException impossible) { + throw new AssertionError(impossible); + } + + if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { + return null; + } + + char sign = id.charAt(3); + int raw = (hour * 3600000) + (minute * 60000); + if (sign == '-') { + raw = -raw; + } + + String cleanId = String.format((Locale) null, "GMT%c%02d:%02d", sign, hour, minute); + return new SimpleTimeZone(raw, cleanId); + } + } /** * The short display name style, such as {@code PDT}. Requests for this @@ -368,7 +412,7 @@ public abstract class TimeZone implements Serializable, Cloneable { // Custom time zone? if (zone == null && id.length() > 3 && id.startsWith("GMT")) { - zone = getCustomTimeZone(id); + zone = CustomTimeZoneParser.getCustomTimeZone(id); } // We never return null; on failure we return the equivalent of "GMT". @@ -376,40 +420,6 @@ public abstract class TimeZone implements Serializable, Cloneable { } /** - * Returns a new SimpleTimeZone for an ID of the form "GMT[+|-]hh[[:]mm]", or null. - */ - private static TimeZone getCustomTimeZone(String id) { - Matcher m = CUSTOM_ZONE_ID_PATTERN.matcher(id); - if (!m.matches()) { - return null; - } - - int hour; - int minute = 0; - try { - hour = Integer.parseInt(m.group(1)); - if (m.group(3) != null) { - minute = Integer.parseInt(m.group(3)); - } - } catch (NumberFormatException impossible) { - throw new AssertionError(impossible); - } - - if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { - return null; - } - - char sign = id.charAt(3); - int raw = (hour * 3600000) + (minute * 60000); - if (sign == '-') { - raw = -raw; - } - - String cleanId = String.format("GMT%c%02d:%02d", sign, hour, minute); - return new SimpleTimeZone(raw, cleanId); - } - - /** * Returns true if {@code timeZone} has the same rules as this time zone. * * <p>The base implementation returns true if both time zones have the same diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java index 803cd49..4a76104 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentSkipListMap.java @@ -293,11 +293,13 @@ public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V> private static final long serialVersionUID = -8627078645895051609L; - /** - * Generates the initial random seed for the cheaper per-instance - * random number generators used in randomLevel. - */ - private static final Random seedGenerator = new Random(); +// BEGIN android-removed +// /** +// * Generates the initial random seed for the cheaper per-instance +// * random number generators used in randomLevel. +// */ +// private static final Random seedGenerator = new Random(); +// END android-removed /** * Special value used to identify base-level header @@ -341,7 +343,13 @@ public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V> entrySet = null; values = null; descendingMap = null; - randomSeed = seedGenerator.nextInt() | 0x0100; // ensure nonzero + // BEGIN android-changed + // + // Most processes are forked from the zygote, so they'll end up + // with the same random seed unless we take additional post fork + // measures. + randomSeed = Math.randomIntInternal() | 0x0100; // ensure nonzero + // END android-changed head = new HeadIndex<K,V>(new Node<K,V>(null, BASE_HEADER, null), null, null, 1); } diff --git a/luni/src/main/java/java/util/jar/Manifest.java b/luni/src/main/java/java/util/jar/Manifest.java index 6a3936d..5a6b42d 100644 --- a/luni/src/main/java/java/util/jar/Manifest.java +++ b/luni/src/main/java/java/util/jar/Manifest.java @@ -41,8 +41,10 @@ public class Manifest implements Cloneable { private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' }; - private final Attributes mainAttributes; - private final HashMap<String, Attributes> entries; + /* non-final for {@code #clone()} */ + private Attributes mainAttributes; + /* non-final for {@code #clone()} */ + private HashMap<String, Attributes> entries; static final class Chunk { final int start; @@ -93,9 +95,7 @@ public class Manifest implements Cloneable { */ @SuppressWarnings("unchecked") public Manifest(Manifest man) { - mainAttributes = (Attributes) man.mainAttributes.clone(); - entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man - .getEntries()).clone(); + cloneAttributesAndEntriesFrom(man); } Manifest(byte[] manifestBytes, boolean readChunks) throws IOException { @@ -156,7 +156,21 @@ public class Manifest implements Cloneable { */ @Override public Object clone() { - return new Manifest(this); + Manifest result; + try { + result = (Manifest) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + + result.cloneAttributesAndEntriesFrom(this); + return result; + } + + private final void cloneAttributesAndEntriesFrom(Manifest other) { + mainAttributes = (Attributes) other.mainAttributes.clone(); + entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) other + .getEntries()).clone(); } /** diff --git a/luni/src/main/java/java/util/jar/StrictJarFile.java b/luni/src/main/java/java/util/jar/StrictJarFile.java index 4a8af5f..a73ca2a 100644 --- a/luni/src/main/java/java/util/jar/StrictJarFile.java +++ b/luni/src/main/java/java/util/jar/StrictJarFile.java @@ -24,6 +24,7 @@ import java.io.RandomAccessFile; import java.security.cert.Certificate; import java.util.HashMap; import java.util.Iterator; +import java.util.Set; import java.util.zip.Inflater; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -53,7 +54,7 @@ public final class StrictJarFile { private final CloseGuard guard = CloseGuard.get(); private boolean closed; - public StrictJarFile(String fileName) throws IOException { + public StrictJarFile(String fileName) throws IOException, SecurityException { this.nativeHandle = nativeOpenJarFile(fileName); this.raf = new RandomAccessFile(fileName, "r"); @@ -64,11 +65,18 @@ public final class StrictJarFile { HashMap<String, byte[]> metaEntries = getMetaEntries(); this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true); this.verifier = new JarVerifier(fileName, manifest, metaEntries); + Set<String> files = this.manifest.getEntries().keySet(); + for (String file : files) { + if (findEntry(file) == null) { + throw new SecurityException(fileName + ": File " + file + " in manifest does not exist"); + } + } isSigned = verifier.readCertificates() && verifier.isSignedJar(); - } catch (IOException ioe) { + } catch (IOException | SecurityException e) { nativeClose(this.nativeHandle); - throw ioe; + IoUtils.closeQuietly(this.raf); + throw e; } guard.open("close"); diff --git a/luni/src/main/java/java/util/logging/FileHandler.java b/luni/src/main/java/java/util/logging/FileHandler.java index 6ffef87..1fd1dbf 100644 --- a/luni/src/main/java/java/util/logging/FileHandler.java +++ b/luni/src/main/java/java/util/logging/FileHandler.java @@ -261,15 +261,15 @@ public class FileHandler extends StreamHandler { boolean hasUniqueID = false; boolean hasGeneration = false; - // TODO privilege code? + String homePath = System.getProperty("user.home"); + if (homePath == null) { + throw new NullPointerException("System property \"user.home\" is null"); + } + boolean homePathHasSepEnd = homePath.endsWith(File.separator); String tempPath = System.getProperty("java.io.tmpdir"); - boolean tempPathHasSepEnd = (tempPath == null ? false : tempPath - .endsWith(File.separator)); - - String homePath = System.getProperty("user.home"); - boolean homePathHasSepEnd = (homePath == null ? false : homePath - .endsWith(File.separator)); + tempPath = tempPath == null ? homePath : tempPath; + boolean tempPathHasSepEnd = tempPath.endsWith(File.separator); StringBuilder sb = new StringBuilder(); pattern = pattern.replace('/', File.separatorChar); diff --git a/luni/src/main/java/java/util/logging/SocketHandler.java b/luni/src/main/java/java/util/logging/SocketHandler.java index 48bfc0e..5de847a 100644 --- a/luni/src/main/java/java/util/logging/SocketHandler.java +++ b/luni/src/main/java/java/util/logging/SocketHandler.java @@ -17,6 +17,7 @@ package java.util.logging; +import libcore.net.NetworkSecurityPolicy; import java.io.BufferedOutputStream; import java.io.IOException; import java.net.Socket; @@ -106,14 +107,21 @@ public class SocketHandler extends StreamHandler { throw new IllegalArgumentException("host == null || host.isEmpty()"); } // check the validity of the port number - int p = 0; + int p; try { p = Integer.parsePositiveInt(port); + // Must be >= 0 to get this far. 0 is invalid too. + if (p == 0) { + throw new IllegalArgumentException("Illegal port argument " + port); + } } catch (NumberFormatException e) { throw new IllegalArgumentException("Illegal port argument " + port); } // establish the network connection try { + if (!NetworkSecurityPolicy.isCleartextTrafficPermitted()) { + throw new IOException("Cleartext traffic not permitted"); + } this.socket = new Socket(host, p); } catch (IOException e) { getErrorManager().error("Failed to establish the network connection", e, diff --git a/luni/src/main/java/java/util/logging/XMLFormatter.java b/luni/src/main/java/java/util/logging/XMLFormatter.java index 0d80b3e..3952596 100644 --- a/luni/src/main/java/java/util/logging/XMLFormatter.java +++ b/luni/src/main/java/java/util/logging/XMLFormatter.java @@ -17,6 +17,7 @@ package java.util.logging; +import java.io.IOException; import java.text.MessageFormat; import java.util.Date; import java.util.ResourceBundle; @@ -50,7 +51,7 @@ public class XMLFormatter extends Formatter { // call a method of LogRecord to ensure not null long time = r.getMillis(); // format to date - String date = MessageFormat.format("{0, date} {0, time}", new Object[] { new Date(time) }); + String date = MessageFormat.format("{0, date} {0, time}", new Date(time)); String nl = System.lineSeparator(); StringBuilder sb = new StringBuilder(); @@ -59,21 +60,21 @@ public class XMLFormatter extends Formatter { append(sb, 1, "millis", time); append(sb, 1, "sequence", r.getSequenceNumber()); if (r.getLoggerName() != null) { - append(sb, 1, "logger", r.getLoggerName()); + escapeAndAppend(sb, 1, "logger", r.getLoggerName()); } append(sb, 1, "level", r.getLevel().getName()); if (r.getSourceClassName() != null) { append(sb, 1, "class", r.getSourceClassName()); } if (r.getSourceMethodName() != null) { - append(sb, 1, "method", r.getSourceMethodName()); + escapeAndAppend(sb, 1, "method", r.getSourceMethodName()); } append(sb, 1, "thread", r.getThreadID()); formatMessages(r, sb); Object[] params = r.getParameters(); if (params != null) { for (Object element : params) { - append(sb, 1, "param", element); + escapeAndAppend(sb, 1, "param", element); } } formatThrowable(r, sb); @@ -96,14 +97,14 @@ public class XMLFormatter extends Formatter { if (message == null) { message = pattern; - append(sb, 1, "message", message); + escapeAndAppend(sb, 1, "message", message); } else { - append(sb, 1, "message", message); - append(sb, 1, "key", pattern); - append(sb, 1, "catalog", r.getResourceBundleName()); + escapeAndAppend(sb, 1, "message", message); + escapeAndAppend(sb, 1, "key", pattern); + escapeAndAppend(sb, 1, "catalog", r.getResourceBundleName()); } } else if (pattern != null) { - append(sb, 1, "message", pattern); + escapeAndAppend(sb, 1, "message", pattern); } else { sb.append(indent).append("<message/>"); } @@ -114,13 +115,13 @@ public class XMLFormatter extends Formatter { if ((t = r.getThrown()) != null) { String nl = System.lineSeparator(); sb.append(indent).append("<exception>").append(nl); - append(sb, 2, "message", t.toString()); + escapeAndAppend(sb, 2, "message", t.toString()); // format throwable's stack trace StackTraceElement[] elements = t.getStackTrace(); for (StackTraceElement e : elements) { sb.append(indent).append(indent).append("<frame>").append(nl); append(sb, 3, "class", e.getClassName()); - append(sb, 3, "method", e.getMethodName()); + escapeAndAppend(sb, 3, "method", e.getMethodName()); append(sb, 3, "line", e.getLineNumber()); sb.append(indent).append(indent).append("</frame>").append(nl); } @@ -138,6 +139,49 @@ public class XMLFormatter extends Formatter { sb.append(System.lineSeparator()); } + private static void escapeAndAppend(StringBuilder sb, int indentCount, String tag, Object value) { + if (value == null) { + append(sb, indentCount, tag, value); + } else { + for (int i = 0; i < indentCount; ++i) { + sb.append(indent); + } + sb.append("<").append(tag).append(">"); + try { + escapeXml(sb, value.toString()); + } catch (IOException e) { + throw new AssertionError(); + } + sb.append("</").append(tag).append(">"); + sb.append(System.lineSeparator()); + } + } + + private static void escapeXml(Appendable valueBuilder, String value) throws IOException { + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + switch (c) { + case '\"': + valueBuilder.append("""); + break; + case '>': + valueBuilder.append(">"); + break; + case '<': + valueBuilder.append("<"); + break; + case '&': + valueBuilder.append("&"); + break; + case '\'': + valueBuilder.append("'"); + break; + default: + valueBuilder.append(c); + } + } + } + /** * Returns the header string for a set of log records formatted as XML * strings, using the output handler's encoding if it is defined, otherwise diff --git a/luni/src/main/java/java/util/zip/GZIPInputStream.java b/luni/src/main/java/java/util/zip/GZIPInputStream.java index 1bfc496..925e8c4 100644 --- a/luni/src/main/java/java/util/zip/GZIPInputStream.java +++ b/luni/src/main/java/java/util/zip/GZIPInputStream.java @@ -222,7 +222,7 @@ public class GZIPInputStream extends InflaterInputStream { if (hcrc) { crc.update(header, 0, 2); } - int length = Memory.peekShort(scratch, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff; + int length = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff; while (length > 0) { int max = length > scratch.length ? scratch.length : length; int result = in.read(scratch, 0, max); @@ -243,7 +243,7 @@ public class GZIPInputStream extends InflaterInputStream { } if (hcrc) { Streams.readFully(in, header, 0, 2); - short crc16 = Memory.peekShort(scratch, 0, ByteOrder.LITTLE_ENDIAN); + short crc16 = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); if ((short) crc.getValue() != crc16) { throw new IOException("CRC mismatch"); } diff --git a/luni/src/main/java/java/util/zip/Zip64.java b/luni/src/main/java/java/util/zip/Zip64.java new file mode 100644 index 0000000..9be3d1c --- /dev/null +++ b/luni/src/main/java/java/util/zip/Zip64.java @@ -0,0 +1,401 @@ +/* + * 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 java.util.zip; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static java.util.zip.ZipOutputStream.writeIntAsUint16; +import static java.util.zip.ZipOutputStream.writeLongAsUint32; +import static java.util.zip.ZipOutputStream.writeLongAsUint64; + +/** + * @hide + */ +public class Zip64 { + + /* Non instantiable */ + private Zip64() {} + + /** + * The maximum supported entry / archive size for standard (non zip64) entries and archives. + */ + static final long MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE = 0x00000000ffffffffL; + + /** + * The header ID of the zip64 extended info header. This value is used to identify + * zip64 data in the "extra" field in the file headers. + */ + private static final short ZIP64_EXTENDED_INFO_HEADER_ID = 0x0001; + + /** + * The minimum size of the zip64 extended info header. This excludes the 2 byte header ID + * and the 2 byte size. + */ + private static final int ZIP64_EXTENDED_INFO_MIN_SIZE = 28; + + /* + * Size (in bytes) of the zip64 end of central directory locator. This will be located + * immediately before the end of central directory record if a given zipfile is in the + * zip64 format. + */ + private static final int ZIP64_LOCATOR_SIZE = 20; + + /** + * The zip64 end of central directory locator signature (4 bytes wide). + */ + private static final int ZIP64_LOCATOR_SIGNATURE = 0x07064b50; + + /** + * The zip64 end of central directory record singature (4 bytes wide). + */ + private static final int ZIP64_EOCD_RECORD_SIGNATURE = 0x06064b50; + + /** + * The "effective" size of the zip64 eocd record. This excludes the fields that + * are proprietary, signature, or fields we aren't interested in. We include the + * following (contiguous) fields in this calculation : + * - disk number (4 bytes) + * - disk with start of central directory (4 bytes) + * - number of central directory entries on this disk (8 bytes) + * - total number of central directory entries (8 bytes) + * - size of the central directory (8 bytes) + * - offset of the start of the central directory (8 bytes) + */ + private static final int ZIP64_EOCD_RECORD_EFFECTIVE_SIZE = 40; + + /** + * Parses the zip64 end of central directory record locator. The locator + * must be placed immediately before the end of central directory (eocd) record + * starting at {@code eocdOffset}. + * + * The position of the file cursor for {@code raf} after a call to this method + * is undefined an callers must reposition it after each call to this method. + */ + public static long parseZip64EocdRecordLocator(RandomAccessFile raf, long eocdOffset) + throws IOException { + // The spec stays curiously silent about whether a zip file with an EOCD record, + // a zip64 locator and a zip64 eocd record is considered "empty". In our implementation, + // we parse all records and read the counts from them instead of drawing any size or + // layout based information. + if (eocdOffset > ZIP64_LOCATOR_SIZE) { + raf.seek(eocdOffset - ZIP64_LOCATOR_SIZE); + if (Integer.reverseBytes(raf.readInt()) == ZIP64_LOCATOR_SIGNATURE) { + byte[] zip64EocdLocator = new byte[ZIP64_LOCATOR_SIZE - 4]; + raf.readFully(zip64EocdLocator); + ByteBuffer buf = ByteBuffer.wrap(zip64EocdLocator).order(ByteOrder.LITTLE_ENDIAN); + + final int diskWithCentralDir = buf.getInt(); + final long zip64EocdRecordOffset = buf.getLong(); + final int numDisks = buf.getInt(); + + if (numDisks != 1 || diskWithCentralDir != 0) { + throw new ZipException("Spanned archives not supported"); + } + + return zip64EocdRecordOffset; + } + } + + return -1; + } + + public static ZipFile.EocdRecord parseZip64EocdRecord(RandomAccessFile raf, + long eocdRecordOffset, int commentLength) throws IOException { + raf.seek(eocdRecordOffset); + final int signature = Integer.reverseBytes(raf.readInt()); + if (signature != ZIP64_EOCD_RECORD_SIGNATURE) { + throw new ZipException("Invalid zip64 eocd record offset, sig=" + + Integer.toHexString(signature) + " offset=" + eocdRecordOffset); + } + + // The zip64 eocd record specifies its own size as an 8 byte integral type. It is variable + // length because of the "zip64 extensible data sector" but that field is reserved for + // pkware's proprietary use. We therefore disregard it altogether and treat the end of + // central directory structure as fixed length. + // + // We also skip "version made by" (2 bytes) and "version needed to extract" (2 bytes) + // fields. We perform additional validation at the ZipEntry level, where applicable. + // + // That's a total of 12 bytes to skip + raf.skipBytes(12); + + byte[] zip64Eocd = new byte[ZIP64_EOCD_RECORD_EFFECTIVE_SIZE]; + raf.readFully(zip64Eocd); + + ByteBuffer buf = ByteBuffer.wrap(zip64Eocd).order(ByteOrder.LITTLE_ENDIAN); + try { + int diskNumber = buf.getInt(); + int diskWithCentralDirStart = buf.getInt(); + long numEntries = buf.getLong(); + long totalNumEntries = buf.getLong(); + buf.getLong(); // Ignore the size of the central directory + long centralDirOffset = buf.getLong(); + + if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDirStart != 0) { + throw new ZipException("Spanned archives not supported :" + + " numEntries=" + numEntries + ", totalNumEntries=" + totalNumEntries + + ", diskNumber=" + diskNumber + ", diskWithCentralDirStart=" + + diskWithCentralDirStart); + } + + return new ZipFile.EocdRecord(numEntries, centralDirOffset, commentLength); + } catch (BufferUnderflowException bue) { + ZipException zipException = new ZipException("Error parsing zip64 eocd record."); + zipException.initCause(bue); + throw zipException; + } + } + + /** + * Parse the zip64 extended info record from the extras present in {@code ze}. + * + * If {@code fromCentralDirectory} is true, we assume we're parsing a central directory + * record. We assume a local file header otherwise. The difference between the two is that + * a central directory entry is required to be complete, whereas a local file header isn't. + * This is due to the presence of an optional data descriptor after the file content. + * + * @return {@code} true iff. a zip64 extended info record was found. + */ + public static boolean parseZip64ExtendedInfo(ZipEntry ze, boolean fromCentralDirectory) + throws ZipException { + int extendedInfoSize = -1; + int extendedInfoStart = -1; + // If this file contains a zip64 central directory locator, entries might + // optionally contain a zip64 extended information extra entry. + if (ze.extra != null && ze.extra.length > 0) { + // Extensible data fields are of the form header1+data1 + header2+data2 and so + // on, where each header consists of a 2 byte header ID followed by a 2 byte size. + // We need to iterate through the entire list of headers to find the header ID + // for the zip64 extended information extra field (0x0001). + final ByteBuffer buf = ByteBuffer.wrap(ze.extra).order(ByteOrder.LITTLE_ENDIAN); + extendedInfoSize = getZip64ExtendedInfoSize(buf); + if (extendedInfoSize != -1) { + extendedInfoStart = buf.position(); + try { + if (extendedInfoSize < ZIP64_EXTENDED_INFO_MIN_SIZE) { + throw new ZipException("Invalid zip64 extended info size: " + extendedInfoSize); + } + + // The size & compressed size only make sense in the central directory *or* if + // we know them beforehand. If we don't know them beforehand, they're stored in + // the data descriptor and should be read from there. + if (fromCentralDirectory || (ze.getMethod() == ZipEntry.STORED)) { + final long zip64Size = buf.getLong(); + if (ze.size == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) { + ze.size = zip64Size; + } + + final long zip64CompressedSize = buf.getLong(); + if (ze.compressedSize == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) { + ze.compressedSize = zip64CompressedSize; + } + } + + // The local header offset is significant only in the central directory. It makes no + // sense within the local header itself. + if (fromCentralDirectory) { + final long zip64LocalHeaderRelOffset = buf.getLong(); + if (ze.localHeaderRelOffset == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) { + ze.localHeaderRelOffset = zip64LocalHeaderRelOffset; + } + } + } catch (BufferUnderflowException bue) { + ZipException zipException = new ZipException("Error parsing extendend info "); + zipException.initCause(bue); + throw zipException; + } + } + } + + // This entry doesn't contain a zip64 extended information data entry header. + // We have to check that the compressedSize / size / localHeaderRelOffset values + // are valid and don't require the presence of the extended header. + if (extendedInfoSize == -1) { + if (ze.compressedSize == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE || + ze.size == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE || + ze.localHeaderRelOffset == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) { + throw new ZipException("File contains no zip64 extended information: " + + "name=" + ze.name + "compressedSize=" + ze.compressedSize + ", size=" + + ze.size + ", localHeader=" + ze.localHeaderRelOffset); + } + + return false; + } else { + // If we're parsed the zip64 extended info header, we remove it from the extras + // so that applications that set their own extras will see the data they set. + + // This is an unfortunate workaround needed due to a gap in the spec. The spec demands + // that extras are present in the "extensible" format, which means that each extra field + // must be prefixed with a header ID and a length. However, earlier versions of the spec + // made no mention of this, nor did any existing API enforce it. This means users could + // set "free form" extras without caring very much whether the implementation wanted to + // extend or add to them. + + // The start of the extended info header. + final int extendedInfoHeaderStart = extendedInfoStart - 4; + // The total size of the extended info, including the header. + final int extendedInfoTotalSize = extendedInfoSize + 4; + + final int extrasLen = ze.extra.length - extendedInfoTotalSize; + byte[] extrasWithoutZip64 = new byte[extrasLen]; + + System.arraycopy(ze.extra, 0, extrasWithoutZip64, 0, extendedInfoHeaderStart); + System.arraycopy(ze.extra, extendedInfoHeaderStart + extendedInfoTotalSize, + extrasWithoutZip64, extendedInfoHeaderStart, (extrasLen - extendedInfoHeaderStart)); + + ze.extra = extrasWithoutZip64; + return true; + } + } + + /** + * Appends a zip64 extended info record to the extras contained in {@code ze}. If {@code ze} + * contains no extras, a new extras array is created. + */ + public static void insertZip64ExtendedInfoToExtras(ZipEntry ze) throws ZipException { + final byte[] output; + // We add 4 to ZIP64_EXTENDED_INFO_MIN_SIZE to account for the 2 byte header and length. + final int extendedInfoSize = ZIP64_EXTENDED_INFO_MIN_SIZE + 4; + if (ze.extra == null) { + output = new byte[extendedInfoSize]; + } else { + // If the existing extras are already too big, we have no choice but to throw + // an error. + if (ze.extra.length + extendedInfoSize > 65535) { + throw new ZipException("No space in extras for zip64 extended entry info"); + } + + // We copy existing extras over and put the zip64 extended info at the beginning. This + // is to avoid breakages in the presence of "old style" extras which don't contain + // headers and lengths. The spec is again silent about these inconsistencies. + // + // This means that people that for ZipOutputStream users, the value ZipEntry.getExtra + // after an entry is written will be different from before. This shouldn't be an issue + // in practice. + output = new byte[ze.extra.length + ZIP64_EXTENDED_INFO_MIN_SIZE + 4]; + System.arraycopy(ze.extra, 0, output, ZIP64_EXTENDED_INFO_MIN_SIZE + 4, ze.extra.length); + } + + ByteBuffer bb = ByteBuffer.wrap(output).order(ByteOrder.LITTLE_ENDIAN); + bb.putShort(ZIP64_EXTENDED_INFO_HEADER_ID); + bb.putShort((short) ZIP64_EXTENDED_INFO_MIN_SIZE); + + if (ze.getMethod() == ZipEntry.STORED) { + bb.putLong(ze.size); + bb.putLong(ze.compressedSize); + } else { + // Store these fields in the data descriptor instead. + bb.putLong(0); // size. + bb.putLong(0); // compressed size. + } + + // The offset is only relevant in the central directory entry, but we write it out here + // anyway, since we know what it is. + bb.putLong(ze.localHeaderRelOffset); + bb.putInt(0); // disk number + + ze.extra = output; + } + + /** + * Returns the size of the extended info record if {@code extras} contains a zip64 extended info + * record, {@code -1} otherwise. The buffer will be positioned at the start of the extended info + * record. + */ + private static int getZip64ExtendedInfoSize(ByteBuffer extras) { + try { + while (extras.hasRemaining()) { + final int headerId = extras.getShort() & 0xffff; + final int length = extras.getShort() & 0xffff; + if (headerId == ZIP64_EXTENDED_INFO_HEADER_ID) { + if (extras.remaining() >= length) { + return length; + } else { + return -1; + } + } else { + extras.position(extras.position() + length); + } + } + + return -1; + } catch (BufferUnderflowException bue) { + // We'll underflow if we have an incomplete header in our extras. + return -1; + } catch (IllegalArgumentException iae) { + // ByteBuffer.position() will throw if we have a truncated extra or + // an invalid length in the header. + return -1; + } + } + + /** + * Copy the size, compressed size and local header offset fields from {@code ze} to + * inside {@code ze}'s extended info record. This is additional step is necessary when + * we could calculate the correct sizes only after writing out the entry. In this case, + * the local file header would not contain real sizes, and they would be present in the + * data descriptor and the central directory only. + */ + public static void refreshZip64ExtendedInfo(ZipEntry ze) { + if (ze.extra == null || ze.extra.length < ZIP64_EXTENDED_INFO_MIN_SIZE) { + throw new IllegalStateException("Zip64 entry has no available extras: " + ze); + } + + + ByteBuffer buf = ByteBuffer.wrap(ze.extra).order(ByteOrder.LITTLE_ENDIAN); + if (getZip64ExtendedInfoSize(buf) == -1) { + throw new IllegalStateException( + "Zip64 entry extras has no zip64 extended info record: " + ze); + } + + buf.putLong(ze.size); + buf.putLong(ze.compressedSize); + buf.putLong(ze.localHeaderRelOffset); + buf.putInt(0); // disk number. + } + + public static void writeZip64EocdRecordAndLocator(ByteArrayOutputStream baos, + long numEntries, long offset, long cDirSize) throws IOException { + // Step 1: Write out the zip64 EOCD record. + writeLongAsUint32(baos, ZIP64_EOCD_RECORD_SIGNATURE); + // The size of the zip64 eocd record. This is the effective size + the + // size of the "version made by" (2 bytes) and the "version needed to extract" (2 bytes) + // fields. + writeLongAsUint64(baos, ZIP64_EOCD_RECORD_EFFECTIVE_SIZE + 4); + // TODO: What values should we put here ? The pre-zip64 values we've chosen don't + // seem to make much sense either. + writeIntAsUint16(baos, 20); + writeIntAsUint16(baos, 20); + writeLongAsUint32(baos, 0L); // number of disk + writeLongAsUint32(baos, 0L); // number of disk with start of central dir. + writeLongAsUint64(baos, numEntries); // number of entries in this disk. + writeLongAsUint64(baos, numEntries); // number of entries in total. + writeLongAsUint64(baos, cDirSize); // size of the central directory. + writeLongAsUint64(baos, offset); // offset of the central directory wrt. this file. + + // Step 2: Write out the zip64 EOCD record locator. + writeLongAsUint32(baos, ZIP64_LOCATOR_SIGNATURE); + writeLongAsUint32(baos, 0); // number of disk with start of central dir. + writeLongAsUint64(baos, offset + cDirSize); // offset of the eocd record wrt. this file. + writeLongAsUint32(baos, 1); // total number of disks. + } +} diff --git a/luni/src/main/java/java/util/zip/ZipEntry.java b/luni/src/main/java/java/util/zip/ZipEntry.java index 217cc3c..26f6863 100644 --- a/luni/src/main/java/java/util/zip/ZipEntry.java +++ b/luni/src/main/java/java/util/zip/ZipEntry.java @@ -52,7 +52,6 @@ public class ZipEntry implements ZipConstants, Cloneable { byte[] extra; - int nameLength = -1; long localHeaderRelOffset = -1; long dataOffset = -1; @@ -69,7 +68,7 @@ public class ZipEntry implements ZipConstants, Cloneable { ZipEntry(String name, String comment, long crc, long compressedSize, long size, int compressionMethod, int time, int modDate, byte[] extra, - int nameLength, long localHeaderRelOffset, long dataOffset) { + long localHeaderRelOffset, long dataOffset) { this.name = name; this.comment = comment; this.crc = crc; @@ -79,7 +78,6 @@ public class ZipEntry implements ZipConstants, Cloneable { this.time = time; this.modDate = modDate; this.extra = extra; - this.nameLength = nameLength; this.localHeaderRelOffset = localHeaderRelOffset; this.dataOffset = dataOffset; } @@ -149,6 +147,11 @@ public class ZipEntry implements ZipConstants, Cloneable { /** * Gets the name of this {@code ZipEntry}. * + * <p><em>Security note:</em> Entry names can represent relative paths. {@code foo/../bar} or + * {@code ../bar/baz}, for example. If the entry name is being used to construct a filename + * or as a path component, it must be validated or sanitized to ensure that files are not + * written outside of the intended destination directory. + * * @return the entry name. */ public String getName() { @@ -265,17 +268,15 @@ public class ZipEntry implements ZipConstants, Cloneable { /** * Sets the uncompressed size of this {@code ZipEntry}. * - * @param value - * the uncompressed size for this entry. - * @throws IllegalArgumentException - * if {@code value} < 0 or {@code value} > 0xFFFFFFFFL. + * @param value the uncompressed size for this entry. + * @throws IllegalArgumentException if {@code value < 0}. */ public void setSize(long value) { - if (value >= 0 && value <= 0xFFFFFFFFL) { - size = value; - } else { + if (value < 0) { throw new IllegalArgumentException("Bad size: " + value); } + + size = value; } /** @@ -340,7 +341,6 @@ public class ZipEntry implements ZipConstants, Cloneable { compressionMethod = ze.compressionMethod; modDate = ze.modDate; extra = ze.extra; - nameLength = ze.nameLength; localHeaderRelOffset = ze.localHeaderRelOffset; dataOffset = ze.dataOffset; } @@ -378,7 +378,7 @@ public class ZipEntry implements ZipConstants, Cloneable { * On exit, "in" will be positioned at the start of the next entry * in the Central Directory. */ - ZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset) throws IOException { + ZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset, boolean isZip64) throws IOException { Streams.readFully(cdStream, cdeHdrBuf, 0, cdeHdrBuf.length); BufferIterator it = HeapBufferIterator.iterator(cdeHdrBuf, 0, cdeHdrBuf.length, @@ -412,7 +412,7 @@ public class ZipEntry implements ZipConstants, Cloneable { compressedSize = ((long) it.readInt()) & 0xffffffffL; size = ((long) it.readInt()) & 0xffffffffL; - nameLength = it.readShort() & 0xffff; + int nameLength = it.readShort() & 0xffff; int extraLength = it.readShort() & 0xffff; int commentByteCount = it.readShort() & 0xffff; @@ -437,6 +437,10 @@ public class ZipEntry implements ZipConstants, Cloneable { Streams.readFully(cdStream, commentBytes, 0, commentByteCount); comment = new String(commentBytes, 0, commentBytes.length, charset); } + + if (isZip64) { + Zip64.parseZip64ExtendedInfo(this, true /* from central directory */); + } } private static boolean containsNulByte(byte[] bytes) { diff --git a/luni/src/main/java/java/util/zip/ZipFile.java b/luni/src/main/java/java/util/zip/ZipFile.java index b44156e..307e7fe 100644 --- a/luni/src/main/java/java/util/zip/ZipFile.java +++ b/luni/src/main/java/java/util/zip/ZipFile.java @@ -107,6 +107,18 @@ public class ZipFile implements Closeable, ZipConstants { private final CloseGuard guard = CloseGuard.get(); + static class EocdRecord { + final long numEntries; + final long centralDirOffset; + final int commentLength; + + EocdRecord(long numEntries, long centralDirOffset, int commentLength) { + this.numEntries = numEntries; + this.centralDirOffset = centralDirOffset; + this.commentLength = commentLength; + } + } + /** * Constructs a new {@code ZipFile} allowing read access to the contents of the given file. * @@ -390,9 +402,11 @@ public class ZipFile implements Closeable, ZipConstants { stopOffset = 0; } + long eocdOffset; while (true) { raf.seek(scanOffset); if (Integer.reverseBytes(raf.readInt()) == ENDSIG) { + eocdOffset = scanOffset; break; } @@ -402,41 +416,35 @@ public class ZipFile implements Closeable, ZipConstants { } } - // Read the End Of Central Directory. ENDHDR includes the signature bytes, - // which we've already read. - byte[] eocd = new byte[ENDHDR - 4]; - raf.readFully(eocd); + final long zip64EocdRecordOffset = Zip64.parseZip64EocdRecordLocator(raf, eocdOffset); - // Pull out the information we need. - BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN); - int diskNumber = it.readShort() & 0xffff; - int diskWithCentralDir = it.readShort() & 0xffff; - int numEntries = it.readShort() & 0xffff; - int totalNumEntries = it.readShort() & 0xffff; - it.skip(4); // Ignore centralDirSize. - long centralDirOffset = ((long) it.readInt()) & 0xffffffffL; - int commentLength = it.readShort() & 0xffff; - - if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) { - throw new ZipException("Spanned archives not supported"); - } - - if (commentLength > 0) { - byte[] commentBytes = new byte[commentLength]; + // Seek back past the eocd signature so that we can continue with our search. + // Note that we add 4 bytes to the offset to skip past the signature. + EocdRecord record = parseEocdRecord(raf, eocdOffset + 4, (zip64EocdRecordOffset != -1) /* isZip64 */); + // Read the comment now to avoid an additional seek. We also know the commentLength + // won't change because that information isn't present in the zip64 eocd record. + if (record.commentLength > 0) { + byte[] commentBytes = new byte[record.commentLength]; raf.readFully(commentBytes); comment = new String(commentBytes, 0, commentBytes.length, StandardCharsets.UTF_8); } + // We have a zip64 eocd record : use that for getting the information we need. + if (zip64EocdRecordOffset != -1) { + record = Zip64.parseZip64EocdRecord(raf, zip64EocdRecordOffset, record.commentLength); + } + // Seek to the first CDE and read all entries. // We have to do this now (from the constructor) rather than lazily because the // public API doesn't allow us to throw IOException except from the constructor // or from getInputStream. - RAFStream rafStream = new RAFStream(raf, centralDirOffset); + RAFStream rafStream = new RAFStream(raf, record.centralDirOffset); BufferedInputStream bufferedStream = new BufferedInputStream(rafStream, 4096); byte[] hdrBuf = new byte[CENHDR]; // Reuse the same buffer for each entry. - for (int i = 0; i < numEntries; ++i) { - ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream, StandardCharsets.UTF_8); - if (newEntry.localHeaderRelOffset >= centralDirOffset) { + for (long i = 0; i < record.numEntries; ++i) { + ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream, StandardCharsets.UTF_8, + (zip64EocdRecordOffset != -1) /* isZip64 */); + if (newEntry.localHeaderRelOffset >= record.centralDirOffset) { throw new ZipException("Local file header offset is after central directory"); } String entryName = newEntry.getName(); @@ -446,6 +454,45 @@ public class ZipFile implements Closeable, ZipConstants { } } + private static EocdRecord parseEocdRecord(RandomAccessFile raf, long offset, boolean isZip64) throws IOException { + raf.seek(offset); + + // Read the End Of Central Directory. ENDHDR includes the signature bytes, + // which we've already read. + byte[] eocd = new byte[ENDHDR - 4]; + raf.readFully(eocd); + + BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN); + final long numEntries; + final long centralDirOffset; + if (isZip64) { + numEntries = -1; + centralDirOffset = -1; + + // If we have a zip64 end of central directory record, we skip through the regular + // end of central directory record and use the information from the zip64 eocd record. + // We're still forced to read the comment length (below) since it isn't present in the + // zip64 eocd record. + it.skip(16); + } else { + // If we don't have a zip64 eocd record, we read values from the "regular" + // eocd record. + int diskNumber = it.readShort() & 0xffff; + int diskWithCentralDir = it.readShort() & 0xffff; + numEntries = it.readShort() & 0xffff; + int totalNumEntries = it.readShort() & 0xffff; + it.skip(4); // Ignore centralDirSize. + + centralDirOffset = ((long) it.readInt()) & 0xffffffffL; + if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) { + throw new ZipException("Spanned archives not supported"); + } + } + + final int commentLength = it.readShort() & 0xffff; + return new EocdRecord(numEntries, centralDirOffset, commentLength); + } + static void throwZipException(String msg, int magic) throws ZipException { final String hexString = IntegralToString.intToHexString(magic, true, 8); throw new ZipException(msg + " signature not found; was " + hexString); diff --git a/luni/src/main/java/java/util/zip/ZipInputStream.java b/luni/src/main/java/java/util/zip/ZipInputStream.java index 4c0034e..f3ca74e 100644 --- a/luni/src/main/java/java/util/zip/ZipInputStream.java +++ b/luni/src/main/java/java/util/zip/ZipInputStream.java @@ -81,7 +81,9 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants private ZipEntry currentEntry; - private final byte[] hdrBuf = new byte[LOCHDR - LOCVER]; + private boolean currentEntryIsZip64; + + private final byte[] hdrBuf = new byte[LOCHDR - LOCVER + 8]; private final CRC32 crc = new CRC32(); @@ -159,7 +161,7 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants } try { - readAndVerifyDataDescriptor(inB, out); + readAndVerifyDataDescriptor(inB, out, currentEntryIsZip64); } catch (Exception e) { if (failure == null) { // otherwise we're already going to throw failure = e; @@ -183,16 +185,31 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants } } - private void readAndVerifyDataDescriptor(int inB, int out) throws IOException { + private void readAndVerifyDataDescriptor(long inB, long out, boolean isZip64) throws IOException { if (hasDD) { - Streams.readFully(in, hdrBuf, 0, EXTHDR); + if (isZip64) { + // 8 additional bytes since the compressed / uncompressed size fields + // in the extended header are 8 bytes each, instead of 4 bytes each. + Streams.readFully(in, hdrBuf, 0, EXTHDR + 8); + } else { + Streams.readFully(in, hdrBuf, 0, EXTHDR); + } + int sig = Memory.peekInt(hdrBuf, 0, ByteOrder.LITTLE_ENDIAN); if (sig != (int) EXTSIG) { throw new ZipException(String.format("unknown format (EXTSIG=%x)", sig)); } currentEntry.crc = ((long) Memory.peekInt(hdrBuf, EXTCRC, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL; - currentEntry.compressedSize = ((long) Memory.peekInt(hdrBuf, EXTSIZ, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL; - currentEntry.size = ((long) Memory.peekInt(hdrBuf, EXTLEN, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL; + + if (isZip64) { + currentEntry.compressedSize = Memory.peekLong(hdrBuf, EXTSIZ, ByteOrder.LITTLE_ENDIAN); + // Note that we apply an adjustment of 4 bytes to the offset of EXTLEN to account + // for the 8 byte size for zip64. + currentEntry.size = Memory.peekLong(hdrBuf, EXTLEN + 4, ByteOrder.LITTLE_ENDIAN); + } else { + currentEntry.compressedSize = ((long) Memory.peekInt(hdrBuf, EXTSIZ, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL; + currentEntry.size = ((long) Memory.peekInt(hdrBuf, EXTLEN, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL; + } } if (currentEntry.crc != crc.getValue()) { throw new ZipException("CRC mismatch"); @@ -266,7 +283,11 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants byte[] extraData = new byte[extraLength]; Streams.readFully(in, extraData, 0, extraLength); currentEntry.setExtra(extraData); + currentEntryIsZip64 = Zip64.parseZip64ExtendedInfo(currentEntry, false /* from central directory */); + } else { + currentEntryIsZip64 = false; } + return currentEntry; } diff --git a/luni/src/main/java/java/util/zip/ZipOutputStream.java b/luni/src/main/java/java/util/zip/ZipOutputStream.java index 8278355..7748cfd 100644 --- a/luni/src/main/java/java/util/zip/ZipOutputStream.java +++ b/luni/src/main/java/java/util/zip/ZipOutputStream.java @@ -23,6 +23,8 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashSet; + +import libcore.util.CountingOutputStream; import libcore.util.EmptyArray; /** @@ -85,7 +87,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant private final CRC32 crc = new CRC32(); - private int offset = 0, curOffset = 0; + private long offset = 0; /** The charset-encoded name for the current entry. */ private byte[] nameBytes; @@ -93,6 +95,31 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant /** The charset-encoded comment for the current entry. */ private byte[] entryCommentBytes; + private static final byte[] ZIP64_PLACEHOLDER_BYTES = + new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; + + /** + * Whether this zip file needs a Zip64 EOCD record / zip64 EOCD record locator. This + * will be true if we wrote an entry whose size or compressed size was too large for + * the standard zip format or if we exceeded the maximum number of entries allowed + * in the standard format. + */ + private boolean archiveNeedsZip64EocdRecord; + + /** + * Whether the current entry being processed needs a zip64 extended info record. This + * will be true if the entry is too large for the standard zip format or if the offset + * to the start of the current entry header is greater than 0xFFFFFFFF. + */ + private boolean currentEntryNeedsZip64; + + /** + * Whether we force all entries in this archive to have a zip64 extended info record. + * This of course implies that the {@code currentEntryNeedsZip64} and + * {@code archiveNeedsZip64EocdRecord} are always {@code true}. + */ + private final boolean forceZip64; + /** * Constructs a new {@code ZipOutputStream} that writes a zip file to the given * {@code OutputStream}. @@ -100,7 +127,15 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant * <p>UTF-8 will be used to encode the file comment, entry names and comments. */ public ZipOutputStream(OutputStream os) { - super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); + this(os, false /* forceZip64 */); + } + + /** + * @hide for testing only. + */ + public ZipOutputStream(OutputStream os, boolean forceZip64) { + super(new CountingOutputStream(os), new Deflater(Deflater.DEFAULT_COMPRESSION, true)); + this.forceZip64 = forceZip64; } /** @@ -146,15 +181,30 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant throw new ZipException("Size mismatch"); } } - curOffset = LOCHDR; + + long curOffset = LOCHDR; // Write the DataDescriptor if (currentEntry.getMethod() != STORED) { curOffset += EXTHDR; - writeLong(out, EXTSIG); - writeLong(out, currentEntry.crc = crc.getValue()); - writeLong(out, currentEntry.compressedSize = def.getTotalOut()); - writeLong(out, currentEntry.size = def.getTotalIn()); + + // Data descriptor signature and CRC are 4 bytes each for both zip and zip64. + writeLongAsUint32(out, EXTSIG); + writeLongAsUint32(out, currentEntry.crc = crc.getValue()); + + currentEntry.compressedSize = def.getBytesWritten(); + currentEntry.size = def.getBytesRead(); + + if (currentEntryNeedsZip64) { + // We need an additional 8 bytes to store 8 byte compressed / uncompressed + // sizes. + curOffset += 8; + writeLongAsUint64(out, currentEntry.compressedSize); + writeLongAsUint64(out, currentEntry.size); + } else { + writeLongAsUint32(out, currentEntry.compressedSize); + writeLongAsUint32(out, currentEntry.size); + } } // Update the CentralDirectory // http://www.pkware.com/documents/casestudies/APPNOTE.TXT @@ -163,33 +213,54 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant // Some tools insist that the central directory have the UTF-8 flag. // http://code.google.com/p/android/issues/detail?id=20214 flags |= ZipFile.GPBF_UTF8_FLAG; - writeLong(cDir, CENSIG); - writeShort(cDir, ZIP_VERSION_2_0); // Version this file was made by. - writeShort(cDir, ZIP_VERSION_2_0); // Minimum version needed to extract. - writeShort(cDir, flags); - writeShort(cDir, currentEntry.getMethod()); - writeShort(cDir, currentEntry.time); - writeShort(cDir, currentEntry.modDate); - writeLong(cDir, crc.getValue()); + writeLongAsUint32(cDir, CENSIG); + writeIntAsUint16(cDir, ZIP_VERSION_2_0); // Version this file was made by. + writeIntAsUint16(cDir, ZIP_VERSION_2_0); // Minimum version needed to extract. + writeIntAsUint16(cDir, flags); + writeIntAsUint16(cDir, currentEntry.getMethod()); + writeIntAsUint16(cDir, currentEntry.time); + writeIntAsUint16(cDir, currentEntry.modDate); + writeLongAsUint32(cDir, crc.getValue()); + if (currentEntry.getMethod() == DEFLATED) { - curOffset += writeLong(cDir, def.getTotalOut()); - writeLong(cDir, def.getTotalIn()); + currentEntry.setCompressedSize(def.getBytesWritten()); + currentEntry.setSize(def.getBytesRead()); + curOffset += currentEntry.getCompressedSize(); } else { - curOffset += writeLong(cDir, crc.tbytes); - writeLong(cDir, crc.tbytes); + currentEntry.setCompressedSize(crc.tbytes); + currentEntry.setSize(crc.tbytes); + curOffset += currentEntry.getSize(); } - curOffset += writeShort(cDir, nameBytes.length); + + if (currentEntryNeedsZip64) { + // Refresh the extended info with the compressed size / size before + // writing it to the central directory. + Zip64.refreshZip64ExtendedInfo(currentEntry); + + // NOTE: We would've written out the zip64 extended info locator to the entry + // extras while constructing the local file header. There's no need to do it again + // here. If we do, there will be a size mismatch since we're calculating offsets + // based on the *current* size of the extra data and not based on the size + // at the point of writing the LFH. + writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE); + writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE); + } else { + writeLongAsUint32(cDir, currentEntry.getCompressedSize()); + writeLongAsUint32(cDir, currentEntry.getSize()); + } + + curOffset += writeIntAsUint16(cDir, nameBytes.length); if (currentEntry.extra != null) { - curOffset += writeShort(cDir, currentEntry.extra.length); + curOffset += writeIntAsUint16(cDir, currentEntry.extra.length); } else { - writeShort(cDir, 0); + writeIntAsUint16(cDir, 0); } - writeShort(cDir, entryCommentBytes.length); // Comment length. - writeShort(cDir, 0); // Disk Start - writeShort(cDir, 0); // Internal File Attributes - writeLong(cDir, 0); // External File Attributes - writeLong(cDir, offset); + writeIntAsUint16(cDir, entryCommentBytes.length); // Comment length. + writeIntAsUint16(cDir, 0); // Disk Start + writeIntAsUint16(cDir, 0); // Internal File Attributes + writeLongAsUint32(cDir, 0); // External File Attributes + writeLongAsUint32(cDir, offset); cDir.write(nameBytes); nameBytes = null; if (currentEntry.extra != null) { @@ -228,16 +299,32 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant if (currentEntry != null) { closeEntry(); } - int cdirSize = cDir.size(); + + int cdirEntriesSize = cDir.size(); + if (archiveNeedsZip64EocdRecord) { + Zip64.writeZip64EocdRecordAndLocator(cDir, entries.size(), offset, cdirEntriesSize); + } + // Write Central Dir End - writeLong(cDir, ENDSIG); - writeShort(cDir, 0); // Disk Number - writeShort(cDir, 0); // Start Disk - writeShort(cDir, entries.size()); // Number of entries - writeShort(cDir, entries.size()); // Number of entries - writeLong(cDir, cdirSize); // Size of central dir - writeLong(cDir, offset); // Offset of central dir - writeShort(cDir, commentBytes.length); + writeLongAsUint32(cDir, ENDSIG); + writeIntAsUint16(cDir, 0); // Disk Number + writeIntAsUint16(cDir, 0); // Start Disk + + // Instead of trying to figure out *why* this archive needed a zip64 eocd record, + // just delegate all these values to the zip64 eocd record. + if (archiveNeedsZip64EocdRecord) { + writeIntAsUint16(cDir, 0xFFFF); // Number of entries + writeIntAsUint16(cDir, 0xFFFF); // Number of entries + writeLongAsUint32(cDir, 0xFFFFFFFF); // Size of central dir + writeLongAsUint32(cDir, 0xFFFFFFFF); // Offset of central dir; + } else { + writeIntAsUint16(cDir, entries.size()); // Number of entries + writeIntAsUint16(cDir, entries.size()); // Number of entries + writeLongAsUint32(cDir, cdirEntriesSize); // Size of central dir + writeLongAsUint32(cDir, offset); // Offset of central dir + } + + writeIntAsUint16(cDir, commentBytes.length); if (commentBytes.length > 0) { cDir.write(commentBytes); } @@ -288,14 +375,8 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant } checkOpen(); + checkAndSetZip64Requirements(ze); - if (entries.contains(ze.name)) { - throw new ZipException("Entry already exists: " + ze.name); - } - if (entries.size() == 64*1024-1) { - // TODO: support Zip64. - throw new ZipException("Too many entries for the zip file format's 16-bit entry count"); - } nameBytes = ze.name.getBytes(StandardCharsets.UTF_8); checkSizeIsWithinShort("Name", nameBytes); entryCommentBytes = EmptyArray.BYTE; @@ -310,6 +391,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant ze.setMethod(method); currentEntry = ze; + currentEntry.localHeaderRelOffset = offset; entries.add(currentEntry.name); // Local file header. @@ -318,30 +400,48 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant // Java always outputs UTF-8 filenames. (Before Java 7, the RI didn't set this flag and used // modified UTF-8. From Java 7, when using UTF_8 it sets this flag and uses normal UTF-8.) flags |= ZipFile.GPBF_UTF8_FLAG; - writeLong(out, LOCSIG); // Entry header - writeShort(out, ZIP_VERSION_2_0); // Minimum version needed to extract. - writeShort(out, flags); - writeShort(out, method); + writeLongAsUint32(out, LOCSIG); // Entry header + writeIntAsUint16(out, ZIP_VERSION_2_0); // Minimum version needed to extract. + writeIntAsUint16(out, flags); + writeIntAsUint16(out, method); if (currentEntry.getTime() == -1) { currentEntry.setTime(System.currentTimeMillis()); } - writeShort(out, currentEntry.time); - writeShort(out, currentEntry.modDate); + writeIntAsUint16(out, currentEntry.time); + writeIntAsUint16(out, currentEntry.modDate); if (method == STORED) { - writeLong(out, currentEntry.crc); - writeLong(out, currentEntry.size); - writeLong(out, currentEntry.size); + writeLongAsUint32(out, currentEntry.crc); + + if (currentEntryNeedsZip64) { + // NOTE: According to the spec, we're allowed to use these fields under zip64 + // as long as the sizes are <= 4G (and omit writing the zip64 extended information header). + // + // For simplicity, we write the zip64 extended info here even if we only need it + // in the central directory (i.e, the case where we're turning on zip64 because the + // offset to this entries LFH is > 0xFFFFFFFF). + out.write(ZIP64_PLACEHOLDER_BYTES); // compressed size + out.write(ZIP64_PLACEHOLDER_BYTES); // uncompressed size + } else { + writeLongAsUint32(out, currentEntry.size); + writeLongAsUint32(out, currentEntry.size); + } } else { - writeLong(out, 0); - writeLong(out, 0); - writeLong(out, 0); + writeLongAsUint32(out, 0); + writeLongAsUint32(out, 0); + writeLongAsUint32(out, 0); + } + + writeIntAsUint16(out, nameBytes.length); + + if (currentEntryNeedsZip64) { + Zip64.insertZip64ExtendedInfoToExtras(currentEntry); } - writeShort(out, nameBytes.length); + if (currentEntry.extra != null) { - writeShort(out, currentEntry.extra.length); + writeIntAsUint16(out, currentEntry.extra.length); } else { - writeShort(out, 0); + writeIntAsUint16(out, 0); } out.write(nameBytes); if (currentEntry.extra != null) { @@ -349,6 +449,37 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant } } + private void checkAndSetZip64Requirements(ZipEntry entry) { + final long totalBytesWritten = getBytesWritten(); + final long entriesWritten = entries.size(); + + currentEntryNeedsZip64 = false; + if (forceZip64) { + currentEntryNeedsZip64 = true; + archiveNeedsZip64EocdRecord = true; + return; + } + + // In this particular case, we'll write a zip64 eocd record locator and a zip64 eocd + // record but we won't actually need zip64 extended info records for any of the individual + // entries (unless they trigger the checks below). + if (entriesWritten == 64*1024 - 1) { + archiveNeedsZip64EocdRecord = true; + } + + // Check whether we'll need to write out a zip64 extended info record in both the local file header + // and the central directory. In addition, we will need a zip64 eocd record locator + // and record to mark this archive as zip64. + // + // TODO: This is an imprecise check. When method != STORED it's possible that the compressed + // size will be (slightly) larger than the actual size. How can we improve this ? + if (totalBytesWritten > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE || + (entry.getSize() > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE)) { + currentEntryNeedsZip64 = true; + archiveNeedsZip64EocdRecord = true; + } + } + /** * Sets the comment associated with the file being written. See {@link ZipFile#getComment}. * @throws IllegalArgumentException if the comment is >= 64 Ki encoded bytes. @@ -386,7 +517,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant defaultCompressionMethod = method; } - private long writeLong(OutputStream os, long i) throws IOException { + static long writeLongAsUint32(OutputStream os, long i) throws IOException { // Write out the long value as an unsigned int os.write((int) (i & 0xFF)); os.write((int) (i >> 8) & 0xFF); @@ -395,7 +526,23 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant return i; } - private int writeShort(OutputStream os, int i) throws IOException { + static long writeLongAsUint64(OutputStream os, long i) throws IOException { + int i1 = (int) i; + os.write(i1 & 0xFF); + os.write((i1 >> 8) & 0xFF); + os.write((i1 >> 16) & 0xFF); + os.write((i1 >> 24) & 0xFF); + + int i2 = (int) (i >> 32); + os.write(i2 & 0xFF); + os.write((i2 >> 8) & 0xFF); + os.write((i2 >> 16) & 0xFF); + os.write((i2 >> 24) & 0xFF); + + return i; + } + + static int writeIntAsUint16(OutputStream os, int i) throws IOException { os.write(i & 0xFF); os.write((i >> 8) & 0xFF); return i; @@ -414,6 +561,13 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant throw new ZipException("No active entry"); } + final long totalBytes = crc.tbytes + byteCount; + if ((totalBytes > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) && !currentEntryNeedsZip64) { + throw new IOException("Zip entry size (" + totalBytes + + " bytes) cannot be represented in the zip format (needs Zip64)." + + " Set the entry length using ZipEntry#setLength to use Zip64 where necessary."); + } + if (currentEntry.getMethod() == STORED) { out.write(buffer, offset, byteCount); } else { @@ -434,4 +588,11 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant " bytes"); } } + + private long getBytesWritten() { + // This cast is somewhat messy but less error prone than keeping an + // CountingOutputStream reference around in addition to the FilterOutputStream's + // out. + return ((CountingOutputStream) out).getCount(); + } } diff --git a/luni/src/main/java/javax/crypto/Cipher.java b/luni/src/main/java/javax/crypto/Cipher.java index 2e3b341..66d03ad 100644 --- a/luni/src/main/java/javax/crypto/Cipher.java +++ b/luni/src/main/java/javax/crypto/Cipher.java @@ -366,8 +366,10 @@ public class Cipher { /** * Convenience call when the Key is not available. + * + * @hide */ - private CipherSpi getSpi() { + public CipherSpi getSpi() { return getSpi(null); } @@ -419,7 +421,7 @@ public class Cipher { if (service == null) { return null; } - return tryTransformWithProvider(key, transformParts, type, service); + return tryTransformWithProvider(null, transformParts, type, service); } ArrayList<Provider.Service> services = ENGINE.getServices(transform); if (services == null) { @@ -1005,8 +1007,7 @@ public class Cipher { * the offset in the input to start. * @param inputLen * the length of the input to transform. - * @return the transformed bytes in a new buffer, or {@code null} if the - * input has zero length. + * @return the transformed bytes in a new buffer, or {@code null} if {@code inputLen} is zero. * @throws IllegalStateException * if this cipher instance is not initialized for encryption or * decryption. @@ -1023,7 +1024,7 @@ public class Cipher { throw new IllegalArgumentException("input == null"); } checkInputOffsetAndCount(input.length, inputOffset, inputLen); - if (input.length == 0) { + if (inputLen == 0) { return null; } return getSpi().engineUpdate(input, inputOffset, inputLen); diff --git a/luni/src/main/java/javax/crypto/ExemptionMechanism.java b/luni/src/main/java/javax/crypto/ExemptionMechanism.java index c2d42e6..eede649 100644 --- a/luni/src/main/java/javax/crypto/ExemptionMechanism.java +++ b/luni/src/main/java/javax/crypto/ExemptionMechanism.java @@ -361,9 +361,6 @@ public class ExemptionMechanism { return len; } - /** - * Override to clear any key state in the instance. - */ @Override protected void finalize() { try { super.finalize(); diff --git a/luni/src/main/java/javax/crypto/KeyAgreement.java b/luni/src/main/java/javax/crypto/KeyAgreement.java index abcfd0e..d27aa2e 100644 --- a/luni/src/main/java/javax/crypto/KeyAgreement.java +++ b/luni/src/main/java/javax/crypto/KeyAgreement.java @@ -195,7 +195,7 @@ public class KeyAgreement { if (service == null) { return null; } - return tryAlgorithmWithProvider(key, service); + return tryAlgorithmWithProvider(null, service); } ArrayList<Provider.Service> services = ENGINE.getServices(algorithm); if (services == null) { @@ -252,8 +252,10 @@ public class KeyAgreement { /** * Convenience call when the Key is not available. + * + * @hide */ - private KeyAgreementSpi getSpi() { + public KeyAgreementSpi getSpi() { return getSpi(null); } diff --git a/luni/src/main/java/javax/crypto/Mac.java b/luni/src/main/java/javax/crypto/Mac.java index 5a73dc5..536f0c5 100644 --- a/luni/src/main/java/javax/crypto/Mac.java +++ b/luni/src/main/java/javax/crypto/Mac.java @@ -199,7 +199,7 @@ public class Mac implements Cloneable { if (service == null) { return null; } - return tryAlgorithmWithProvider(key, service); + return tryAlgorithmWithProvider(null, service); } ArrayList<Provider.Service> services = ENGINE.getServices(algorithm); if (services == null) { @@ -266,8 +266,10 @@ public class Mac implements Cloneable { /** * Convenience call when the Key is not available. + * + * @hide */ - private MacSpi getSpi() { + public MacSpi getSpi() { return getSpi(null); } diff --git a/luni/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java b/luni/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java deleted file mode 100644 index fd84c3e..0000000 --- a/luni/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 javax.net.ssl; - -import java.net.InetAddress; -import java.security.cert.Certificate; -import java.security.cert.CertificateParsingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import javax.security.auth.x500.X500Principal; - -/** - * A HostnameVerifier consistent with <a - * href="http://www.ietf.org/rfc/rfc2818.txt">RFC 2818</a>. - * - * @hide accessible via HttpsURLConnection.getDefaultHostnameVerifier() - */ -public final class DefaultHostnameVerifier implements HostnameVerifier { - private static final int ALT_DNS_NAME = 2; - private static final int ALT_IPA_NAME = 7; - - public final boolean verify(String host, SSLSession session) { - try { - Certificate[] certificates = session.getPeerCertificates(); - return verify(host, (X509Certificate) certificates[0]); - } catch (SSLException e) { - return false; - } - } - - public boolean verify(String host, X509Certificate certificate) { - return InetAddress.isNumeric(host) - ? verifyIpAddress(host, certificate) - : verifyHostName(host, certificate); - } - - /** - * Returns true if {@code certificate} matches {@code ipAddress}. - */ - private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) { - for (String altName : getSubjectAltNames(certificate, ALT_IPA_NAME)) { - if (ipAddress.equalsIgnoreCase(altName)) { - return true; - } - } - return false; - } - - /** - * Returns true if {@code certificate} matches {@code hostName}. - */ - private boolean verifyHostName(String hostName, X509Certificate certificate) { - hostName = hostName.toLowerCase(Locale.US); - boolean hasDns = false; - for (String altName : getSubjectAltNames(certificate, ALT_DNS_NAME)) { - hasDns = true; - if (verifyHostName(hostName, altName)) { - return true; - } - } - - if (!hasDns) { - X500Principal principal = certificate.getSubjectX500Principal(); - // RFC 2818 advises using the most specific name for matching. - String cn = new DistinguishedNameParser(principal).findMostSpecific("cn"); - if (cn != null) { - return verifyHostName(hostName, cn); - } - } - - return false; - } - - private List<String> getSubjectAltNames(X509Certificate certificate, int type) { - List<String> result = new ArrayList<String>(); - try { - Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames(); - if (subjectAltNames == null) { - return Collections.emptyList(); - } - for (Object subjectAltName : subjectAltNames) { - List<?> entry = (List<?>) subjectAltName; - if (entry == null || entry.size() < 2) { - continue; - } - Integer altNameType = (Integer) entry.get(0); - if (altNameType == null) { - continue; - } - if (altNameType == type) { - String altName = (String) entry.get(1); - if (altName != null) { - result.add(altName); - } - } - } - return result; - } catch (CertificateParsingException e) { - return Collections.emptyList(); - } - } - - /** - * Returns true if {@code hostName} matches the name or pattern {@code cn}. - * - * @param hostName lowercase host name. - * @param cn certificate host name. May include wildcards like - * {@code *.android.com}. - */ - public boolean verifyHostName(String hostName, String cn) { - if (hostName == null || hostName.isEmpty() || cn == null || cn.isEmpty()) { - return false; - } - - cn = cn.toLowerCase(Locale.US); - - if (!cn.contains("*")) { - return hostName.equals(cn); - } - - if (cn.startsWith("*.") && hostName.equals(cn.substring(2))) { - return true; // "*.foo.com" matches "foo.com" - } - - int asterisk = cn.indexOf('*'); - int dot = cn.indexOf('.'); - if (asterisk > dot) { - return false; // malformed; wildcard must be in the first part of the cn - } - - if (!hostName.regionMatches(0, cn, 0, asterisk)) { - return false; // prefix before '*' doesn't match - } - - int suffixLength = cn.length() - (asterisk + 1); - int suffixStart = hostName.length() - suffixLength; - if (hostName.indexOf('.', asterisk) < suffixStart) { - // TODO: remove workaround for *.clients.google.com http://b/5426333 - if (!hostName.endsWith(".clients.google.com")) { - return false; // wildcard '*' can't match a '.' - } - } - - if (!hostName.regionMatches(suffixStart, cn, asterisk + 1, suffixLength)) { - return false; // suffix after '*' doesn't match - } - - return true; - } -} diff --git a/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java b/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java index ab86a9b..1bd48fd 100644 --- a/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java +++ b/luni/src/main/java/javax/net/ssl/HttpsURLConnection.java @@ -109,7 +109,16 @@ public abstract class HttpsURLConnection extends HttpURLConnection { * it. */ private static class NoPreloadHolder { - public static HostnameVerifier defaultHostnameVerifier = new DefaultHostnameVerifier(); + public static HostnameVerifier defaultHostnameVerifier; + static { + try { + defaultHostnameVerifier = (HostnameVerifier) + Class.forName("com.android.okhttp.internal.tls.OkHostnameVerifier") + .getField("INSTANCE").get(null); + } catch (Exception e) { + throw new AssertionError("Failed to obtain okhttp HostnameVerifier", e); + } + } public static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory .getDefault(); diff --git a/luni/src/main/java/javax/net/ssl/SSLEngine.java b/luni/src/main/java/javax/net/ssl/SSLEngine.java index cbf02ac..f40f4b0 100644 --- a/luni/src/main/java/javax/net/ssl/SSLEngine.java +++ b/luni/src/main/java/javax/net/ssl/SSLEngine.java @@ -73,17 +73,17 @@ import java.nio.ByteBuffer; * <tbody> * <tr> * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td> - * <td>9+</td> + * <td>9-22</td> * <td>9-19</td> * </tr> * <tr> * <td>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td> - * <td>9+</td> + * <td>9-22</td> * <td>9-19</td> * </tr> * <tr> * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td> - * <td>9+</td> + * <td>9-22</td> * <td>9-19</td> * </tr> * <tr> @@ -178,32 +178,32 @@ import java.nio.ByteBuffer; * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td> - * <td>9+</td> - * <td>9+</td> + * <td>9-22</td> + * <td>9-22</td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA256</td> - * <td>20+</td> + * <td>20-22</td> * <td></td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_128_GCM_SHA256</td> - * <td>20+</td> + * <td>20-22</td> * <td></td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</td> - * <td>9+</td> - * <td>20+</td> + * <td>9-22</td> + * <td>20-22</td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA256</td> - * <td>20+</td> + * <td>20-22</td> * <td></td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_256_GCM_SHA384</td> - * <td>20+</td> + * <td>20-22</td> * <td></td> * </tr> * <tr> diff --git a/luni/src/main/java/javax/net/ssl/SSLSocket.java b/luni/src/main/java/javax/net/ssl/SSLSocket.java index dc406e1..c6906c5 100644 --- a/luni/src/main/java/javax/net/ssl/SSLSocket.java +++ b/luni/src/main/java/javax/net/ssl/SSLSocket.java @@ -117,17 +117,17 @@ import java.net.UnknownHostException; * <tbody> * <tr> * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td> - * <td>9+</td> + * <td>9-22</td> * <td>9-19</td> * </tr> * <tr> * <td>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td> - * <td>9+</td> + * <td>9-22</td> * <td>9-19</td> * </tr> * <tr> * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td> - * <td>9+</td> + * <td>9-22</td> * <td>9-19</td> * </tr> * <tr> @@ -212,32 +212,32 @@ import java.net.UnknownHostException; * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td> - * <td>9+</td> - * <td>9+</td> + * <td>9-22</td> + * <td>9-22</td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA256</td> - * <td>20+</td> + * <td>20-22</td> * <td></td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_128_GCM_SHA256</td> - * <td>20+</td> + * <td>20-22</td> * <td></td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</td> - * <td>9+</td> - * <td>11+</td> + * <td>9-22</td> + * <td>11-22</td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA256</td> - * <td>20+</td> + * <td>20-22</td> * <td></td> * </tr> * <tr> * <td>TLS_DHE_DSS_WITH_AES_256_GCM_SHA384</td> - * <td>20+</td> + * <td>20-22</td> * <td></td> * </tr> * <tr> @@ -639,14 +639,14 @@ import java.net.UnknownHostException; * <tr> * <td>DHE-DSS-AES128-SHA</td> * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td> - * <td>1+</td> - * <td>1+</td> + * <td>1-22</td> + * <td>1-22</td> * </tr> * <tr> * <td>DHE-DSS-AES256-SHA</td> * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</td> - * <td>1+</td> - * <td>1-8, 11+</td> + * <td>1-22</td> + * <td>1-8, 11-22</td> * </tr> * <tr> * <td>DHE-RSA-AES128-SHA</td> @@ -663,13 +663,13 @@ import java.net.UnknownHostException; * <tr> * <td>EDH-DSS-DES-CBC-SHA</td> * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td> - * <td>1+</td> + * <td>1-22</td> * <td>1-19</td> * </tr> * <tr> * <td>EDH-DSS-DES-CBC3-SHA</td> * <td>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td> - * <td>1+</td> + * <td>1-22</td> * <td>1-19</td> * </tr> * <tr> @@ -693,7 +693,7 @@ import java.net.UnknownHostException; * <tr> * <td>EXP-EDH-DSS-DES-CBC-SHA</td> * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td> - * <td>1+</td> + * <td>1-22</td> * <td>1-19</td> * </tr> * <tr> diff --git a/luni/src/main/java/javax/security/cert/X509Certificate.java b/luni/src/main/java/javax/security/cert/X509Certificate.java index e85a556..5084ae0 100644 --- a/luni/src/main/java/javax/security/cert/X509Certificate.java +++ b/luni/src/main/java/javax/security/cert/X509Certificate.java @@ -51,7 +51,7 @@ public abstract class X509Certificate extends Certificate { String classname = Security.getProperty("cert.provider.x509v1"); Class cl = Class.forName(classname); constructor = cl.getConstructor(new Class[] {InputStream.class}); - } catch (Throwable e) { + } catch (Exception|LinkageError e) { } } @@ -80,7 +80,7 @@ public abstract class X509Certificate extends Certificate { try { return (X509Certificate) constructor.newInstance(new Object[] {inStream}); - } catch (Throwable e) { + } catch (ReflectiveOperationException e) { throw new CertificateException(e.getMessage()); } } diff --git a/luni/src/main/java/javax/xml/datatype/FactoryFinder.java b/luni/src/main/java/javax/xml/datatype/FactoryFinder.java index b65f412..1fbca2f 100644 --- a/luni/src/main/java/javax/xml/datatype/FactoryFinder.java +++ b/luni/src/main/java/javax/xml/datatype/FactoryFinder.java @@ -47,11 +47,30 @@ final class FactoryFinder { /** <p>Debug flag to trace loading process.</p> */ private static boolean debug = false; - /** <p>Cache properties for performance.</p> */ - private static Properties cacheProps = new Properties(); + /** + * <p>Cache properties for performance. Use a static class to avoid double-checked + * locking.</p> + */ + private static class CacheHolder { - /** <p>First time requires initialization overhead.</p> */ - private static boolean firstTime = true; + private static Properties cacheProps = new Properties(); + + static { + String javah = System.getProperty("java.home"); + String configFile = javah + File.separator + "lib" + File.separator + "jaxp.properties"; + File f = new File(configFile); + if (f.exists()) { + if (debug) debugPrintln("Read properties file " + f); + try { + cacheProps.load(new FileInputStream(f)); + } catch (Exception ex) { + if (debug) { + ex.printStackTrace(); + } + } + } + } + } /** Default columns per line. */ private static final int DEFAULT_LINE_LENGTH = 80; @@ -177,22 +196,7 @@ final class FactoryFinder { // try to read from $java.home/lib/jaxp.properties try { - String javah = System.getProperty("java.home"); - String configFile = javah + File.separator + "lib" + File.separator + "jaxp.properties"; - String factoryClassName = null; - if (firstTime) { - synchronized (cacheProps) { - if (firstTime) { - File f = new File(configFile); - firstTime = false; - if (f.exists()) { - if (debug) debugPrintln("Read properties file " + f); - cacheProps.load(new FileInputStream(f)); - } - } - } - } - factoryClassName = cacheProps.getProperty(factoryId); + String factoryClassName = CacheHolder.cacheProps.getProperty(factoryId); if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties"); if (factoryClassName != null) { diff --git a/luni/src/main/java/javax/xml/validation/SchemaFactoryFinder.java b/luni/src/main/java/javax/xml/validation/SchemaFactoryFinder.java index 636777c..0060612 100644 --- a/luni/src/main/java/javax/xml/validation/SchemaFactoryFinder.java +++ b/luni/src/main/java/javax/xml/validation/SchemaFactoryFinder.java @@ -49,14 +49,29 @@ final class SchemaFactoryFinder { private static boolean debug = false; /** - * <p>Cache properties for performance.</p> + * <p>Cache properties for performance. Use a static class to avoid double-checked + * locking.</p> */ - private static Properties cacheProps = new Properties(); + private static class CacheHolder { - /** - * <p>First time requires initialization overhead.</p> - */ - private static boolean firstTime = true; + private static Properties cacheProps = new Properties(); + + static { + String javah = System.getProperty("java.home"); + String configFile = javah + File.separator + "lib" + File.separator + "jaxp.properties"; + File f = new File(configFile); + if (f.exists()) { + if (debug) debugPrintln("Read properties file " + f); + try { + cacheProps.load(new FileInputStream(f)); + } catch (Exception ex) { + if (debug) { + ex.printStackTrace(); + } + } + } + } + } /** * Default columns per line. @@ -184,27 +199,9 @@ final class SchemaFactoryFinder { } } - String javah = System.getProperty("java.home"); - String configFile = javah + File.separator + - "lib" + File.separator + "jaxp.properties"; - - String factoryClassName = null ; - // try to read from $java.home/lib/jaxp.properties try { - if(firstTime){ - synchronized(cacheProps){ - if(firstTime){ - File f=new File( configFile ); - firstTime = false; - if(f.exists()){ - if (debug) debugPrintln("Read properties file " + f); - cacheProps.load(new FileInputStream(f)); - } - } - } - } - factoryClassName = cacheProps.getProperty(propertyName); + String factoryClassName = CacheHolder.cacheProps.getProperty(propertyName); if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties"); if (factoryClassName != null) { diff --git a/luni/src/main/java/javax/xml/xpath/XPathFactoryFinder.java b/luni/src/main/java/javax/xml/xpath/XPathFactoryFinder.java index 0113e7d..5a7663c 100644 --- a/luni/src/main/java/javax/xml/xpath/XPathFactoryFinder.java +++ b/luni/src/main/java/javax/xml/xpath/XPathFactoryFinder.java @@ -56,14 +56,29 @@ final class XPathFactoryFinder { } /** - * <p>Cache properties for performance.</p> + * <p>Cache properties for performance. Use a static class to avoid double-checked + * locking.</p> */ - private static Properties cacheProps = new Properties(); + private static class CacheHolder { - /** - * <p>First time requires initialization overhead.</p> - */ - private static boolean firstTime = true; + private static Properties cacheProps = new Properties(); + + static { + String javah = System.getProperty("java.home"); + String configFile = javah + File.separator + "lib" + File.separator + "jaxp.properties"; + File f = new File(configFile); + if (f.exists()) { + if (debug) debugPrintln("Read properties file " + f); + try { + cacheProps.load(new FileInputStream(f)); + } catch (Exception ex) { + if (debug) { + ex.printStackTrace(); + } + } + } + } + } /** * <p>Conditional debug printing.</p> @@ -164,27 +179,9 @@ final class XPathFactoryFinder { e.printStackTrace(); } - String javah = System.getProperty("java.home"); - String configFile = javah + File.separator + - "lib" + File.separator + "jaxp.properties"; - - String factoryClassName = null ; - // try to read from $java.home/lib/jaxp.properties try { - if(firstTime){ - synchronized(cacheProps){ - if(firstTime){ - File f=new File( configFile ); - firstTime = false; - if (f.exists()) { - if (debug) debugPrintln("Read properties file " + f); - cacheProps.load(new FileInputStream(f)); - } - } - } - } - factoryClassName = cacheProps.getProperty(propertyName); + String factoryClassName = CacheHolder.cacheProps.getProperty(propertyName); if (debug) debugPrintln("found " + factoryClassName + " in $java.home/jaxp.properties"); if (factoryClassName != null) { diff --git a/luni/src/main/java/libcore/icu/DateIntervalFormat.java b/luni/src/main/java/libcore/icu/DateIntervalFormat.java index 3855654..509d0a0 100644 --- a/luni/src/main/java/libcore/icu/DateIntervalFormat.java +++ b/luni/src/main/java/libcore/icu/DateIntervalFormat.java @@ -16,48 +16,27 @@ package libcore.icu; -import java.util.Calendar; -import java.util.Locale; +import com.ibm.icu.util.Calendar; +import com.ibm.icu.util.ULocale; + +import java.text.FieldPosition; import java.util.TimeZone; import libcore.util.BasicLruCache; +import static libcore.icu.DateUtilsBridge.FORMAT_UTC; + /** - * Exposes icu4c's DateIntervalFormat. + * Exposes icu4j's DateIntervalFormat. */ public final class DateIntervalFormat { - // These are all public API in DateUtils. There are others, but they're either for use with - // other methods (like FORMAT_ABBREV_RELATIVE), don't internationalize (like FORMAT_CAP_AMPM), - // or have never been implemented anyway. - public static final int FORMAT_SHOW_TIME = 0x00001; - public static final int FORMAT_SHOW_WEEKDAY = 0x00002; - public static final int FORMAT_SHOW_YEAR = 0x00004; - public static final int FORMAT_NO_YEAR = 0x00008; - public static final int FORMAT_SHOW_DATE = 0x00010; - public static final int FORMAT_NO_MONTH_DAY = 0x00020; - public static final int FORMAT_12HOUR = 0x00040; - public static final int FORMAT_24HOUR = 0x00080; - public static final int FORMAT_UTC = 0x02000; - public static final int FORMAT_ABBREV_TIME = 0x04000; - public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; - public static final int FORMAT_ABBREV_MONTH = 0x10000; - public static final int FORMAT_NUMERIC_DATE = 0x20000; - public static final int FORMAT_ABBREV_ALL = 0x80000; - - private static final int DAY_IN_MS = 24 * 60 * 60 * 1000; - private static final int EPOCH_JULIAN_DAY = 2440588; - private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); - static class FormatterCache extends BasicLruCache<String, Long> { + static class FormatterCache extends BasicLruCache<String, com.ibm.icu.text.DateIntervalFormat> { FormatterCache() { super(8); } - - protected void entryEvicted(String key, Long value) { - destroyDateIntervalFormat(value); - } - }; + } private DateIntervalFormat() { } @@ -67,131 +46,58 @@ public final class DateIntervalFormat { if ((flags & FORMAT_UTC) != 0) { olsonId = "UTC"; } + // We create a java.util.TimeZone here to use libcore's data and libcore's olson ID / pseudo-tz + // logic. TimeZone tz = (olsonId != null) ? TimeZone.getTimeZone(olsonId) : TimeZone.getDefault(); - return formatDateRange(Locale.getDefault(), tz, startMs, endMs, flags); + com.ibm.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); + ULocale icuLocale = ULocale.getDefault(); + return formatDateRange(icuLocale, icuTimeZone, startMs, endMs, flags); } // This is our slightly more sensible internal API. (A truly sane replacement would take a // skeleton instead of int flags.) - public static String formatDateRange(Locale locale, TimeZone tz, long startMs, long endMs, int flags) { - Calendar startCalendar = Calendar.getInstance(tz); - startCalendar.setTimeInMillis(startMs); - + public static String formatDateRange(ULocale icuLocale, com.ibm.icu.util.TimeZone icuTimeZone, + long startMs, long endMs, int flags) { + Calendar startCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, startMs); Calendar endCalendar; if (startMs == endMs) { endCalendar = startCalendar; } else { - endCalendar = Calendar.getInstance(tz); - endCalendar.setTimeInMillis(endMs); + endCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, endMs); } boolean endsAtMidnight = isMidnight(endCalendar); // If we're not showing the time or the start and end times are on the same day, and the // end time is midnight, fudge the end date so we don't count the day that's about to start. - // This is not the behavior of icu4c's DateIntervalFormat, but it's the historical behavior + // This is not the behavior of icu4j's DateIntervalFormat, but it's the historical behavior // of Android's DateUtils.formatDateRange. if (startMs != endMs && endsAtMidnight && - ((flags & FORMAT_SHOW_TIME) == 0 || dayDistance(startCalendar, endCalendar) <= 1)) { + ((flags & DateUtilsBridge.FORMAT_SHOW_TIME) == 0 + || DateUtilsBridge.dayDistance(startCalendar, endCalendar) <= 1)) { endCalendar.roll(Calendar.DAY_OF_MONTH, false); - endMs -= DAY_IN_MS; } - String skeleton = toSkeleton(startCalendar, endCalendar, flags); + String skeleton = DateUtilsBridge.toSkeleton(startCalendar, endCalendar, flags); synchronized (CACHED_FORMATTERS) { - return formatDateInterval(getFormatter(skeleton, locale.toString(), tz.getID()), startMs, endMs); + com.ibm.icu.text.DateIntervalFormat formatter = + getFormatter(skeleton, icuLocale, icuTimeZone); + return formatter.format(startCalendar, endCalendar, new StringBuffer(), + new FieldPosition(0)).toString(); } } - private static long getFormatter(String skeleton, String localeName, String tzName) { - String key = skeleton + "\t" + localeName + "\t" + tzName; - Long formatter = CACHED_FORMATTERS.get(key); + private static com.ibm.icu.text.DateIntervalFormat getFormatter(String skeleton, ULocale locale, + com.ibm.icu.util.TimeZone icuTimeZone) { + String key = skeleton + "\t" + locale + "\t" + icuTimeZone; + com.ibm.icu.text.DateIntervalFormat formatter = CACHED_FORMATTERS.get(key); if (formatter != null) { return formatter; } - long address = createDateIntervalFormat(skeleton, localeName, tzName); - CACHED_FORMATTERS.put(key, address); - return address; - } - - private static String toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags) { - if ((flags & FORMAT_ABBREV_ALL) != 0) { - flags |= FORMAT_ABBREV_MONTH | FORMAT_ABBREV_TIME | FORMAT_ABBREV_WEEKDAY; - } - - String monthPart = "MMMM"; - if ((flags & FORMAT_NUMERIC_DATE) != 0) { - monthPart = "M"; - } else if ((flags & FORMAT_ABBREV_MONTH) != 0) { - monthPart = "MMM"; - } - - String weekPart = "EEEE"; - if ((flags & FORMAT_ABBREV_WEEKDAY) != 0) { - weekPart = "EEE"; - } - - String timePart = "j"; // "j" means choose 12 or 24 hour based on current locale. - if ((flags & FORMAT_24HOUR) != 0) { - timePart = "H"; - } else if ((flags & FORMAT_12HOUR) != 0) { - timePart = "h"; - } - - // If we've not been asked to abbreviate times, or we're using the 24-hour clock (where it - // never makes sense to leave out the minutes), include minutes. This gets us times like - // "4 PM" while avoiding times like "16" (for "16:00"). - if ((flags & FORMAT_ABBREV_TIME) == 0 || (flags & FORMAT_24HOUR) != 0) { - timePart += "m"; - } else { - // Otherwise, we're abbreviating a 12-hour time, and should only show the minutes - // if they're not both "00". - if (!(onTheHour(startCalendar) && onTheHour(endCalendar))) { - timePart = timePart + "m"; - } - } - - if (fallOnDifferentDates(startCalendar, endCalendar)) { - flags |= FORMAT_SHOW_DATE; - } - - if (fallInSameMonth(startCalendar, endCalendar) && (flags & FORMAT_NO_MONTH_DAY) != 0) { - flags &= (~FORMAT_SHOW_WEEKDAY); - flags &= (~FORMAT_SHOW_TIME); - } - - if ((flags & (FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_WEEKDAY)) == 0) { - flags |= FORMAT_SHOW_DATE; - } - - // If we've been asked to show the date, work out whether we think we should show the year. - if ((flags & FORMAT_SHOW_DATE) != 0) { - if ((flags & FORMAT_SHOW_YEAR) != 0) { - // The caller explicitly wants us to show the year. - } else if ((flags & FORMAT_NO_YEAR) != 0) { - // The caller explicitly doesn't want us to show the year, even if we otherwise would. - } else if (!fallInSameYear(startCalendar, endCalendar) || !isThisYear(startCalendar)) { - flags |= FORMAT_SHOW_YEAR; - } - } - - StringBuilder builder = new StringBuilder(); - if ((flags & (FORMAT_SHOW_DATE | FORMAT_NO_MONTH_DAY)) != 0) { - if ((flags & FORMAT_SHOW_YEAR) != 0) { - builder.append("y"); - } - builder.append(monthPart); - if ((flags & FORMAT_NO_MONTH_DAY) == 0) { - builder.append("d"); - } - } - if ((flags & FORMAT_SHOW_WEEKDAY) != 0) { - builder.append(weekPart); - } - if ((flags & FORMAT_SHOW_TIME) != 0) { - builder.append(timePart); - } - return builder.toString(); + formatter = com.ibm.icu.text.DateIntervalFormat.getInstance(skeleton, locale); + formatter.setTimeZone(icuTimeZone); + CACHED_FORMATTERS.put(key, formatter); + return formatter; } private static boolean isMidnight(Calendar c) { @@ -201,39 +107,4 @@ public final class DateIntervalFormat { c.get(Calendar.MILLISECOND) == 0; } - private static boolean onTheHour(Calendar c) { - return c.get(Calendar.MINUTE) == 0 && c.get(Calendar.SECOND) == 0; - } - - private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) { - return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) || - c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) || - c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH); - } - - private static boolean fallInSameMonth(Calendar c1, Calendar c2) { - return c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH); - } - - private static boolean fallInSameYear(Calendar c1, Calendar c2) { - return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR); - } - - private static boolean isThisYear(Calendar c) { - Calendar now = Calendar.getInstance(c.getTimeZone()); - return c.get(Calendar.YEAR) == now.get(Calendar.YEAR); - } - - private static int dayDistance(Calendar c1, Calendar c2) { - return julianDay(c2) - julianDay(c1); - } - - private static int julianDay(Calendar c) { - long utcMs = c.getTimeInMillis() + c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET); - return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY; - } - - private static native long createDateIntervalFormat(String skeleton, String localeName, String tzName); - private static native void destroyDateIntervalFormat(long address); - private static native String formatDateInterval(long address, long fromDate, long toDate); } diff --git a/luni/src/main/java/libcore/icu/DateTimeFormat.java b/luni/src/main/java/libcore/icu/DateTimeFormat.java new file mode 100644 index 0000000..7323c26 --- /dev/null +++ b/luni/src/main/java/libcore/icu/DateTimeFormat.java @@ -0,0 +1,58 @@ +/* + * 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.icu; + +import com.ibm.icu.text.DateFormat; +import com.ibm.icu.text.DateTimePatternGenerator; +import com.ibm.icu.text.DisplayContext; +import com.ibm.icu.text.SimpleDateFormat; +import com.ibm.icu.util.Calendar; +import com.ibm.icu.util.ULocale; + +import libcore.util.BasicLruCache; + +/** + * A formatter that outputs a single date/time. + */ +public class DateTimeFormat { + private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); + + static class FormatterCache extends BasicLruCache<String, DateFormat> { + FormatterCache() { + super(8); + } + } + + private DateTimeFormat() { + } + + public static String format(ULocale icuLocale, Calendar time, int flags, + DisplayContext displayContext) { + String skeleton = DateUtilsBridge.toSkeleton(time, flags); + String key = skeleton + "\t" + icuLocale + "\t" + time.getTimeZone(); + synchronized(CACHED_FORMATTERS) { + DateFormat formatter = CACHED_FORMATTERS.get(key); + if (formatter == null) { + DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance(icuLocale); + formatter = new SimpleDateFormat(generator.getBestPattern(skeleton), icuLocale); + CACHED_FORMATTERS.put(key, formatter); + } + formatter.setContext(displayContext); + return formatter.format(time); + } + } +} diff --git a/luni/src/main/java/libcore/icu/DateUtilsBridge.java b/luni/src/main/java/libcore/icu/DateUtilsBridge.java new file mode 100644 index 0000000..88faa90 --- /dev/null +++ b/luni/src/main/java/libcore/icu/DateUtilsBridge.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.icu; + +import com.ibm.icu.impl.JavaTimeZone; +import com.ibm.icu.util.Calendar; +import com.ibm.icu.util.GregorianCalendar; +import com.ibm.icu.util.ULocale; + +/** + * Common methods and constants for the various ICU formatters used to support + * android.text.format.DateUtils. + */ +public final class DateUtilsBridge { + // These are all public API in DateUtils. There are others, but they're either for use with + // other methods (like FORMAT_ABBREV_RELATIVE), don't internationalize (like FORMAT_CAP_AMPM), + // or have never been implemented anyway. + public static final int FORMAT_SHOW_TIME = 0x00001; + public static final int FORMAT_SHOW_WEEKDAY = 0x00002; + public static final int FORMAT_SHOW_YEAR = 0x00004; + public static final int FORMAT_NO_YEAR = 0x00008; + public static final int FORMAT_SHOW_DATE = 0x00010; + public static final int FORMAT_NO_MONTH_DAY = 0x00020; + public static final int FORMAT_12HOUR = 0x00040; + public static final int FORMAT_24HOUR = 0x00080; + public static final int FORMAT_UTC = 0x02000; + public static final int FORMAT_ABBREV_TIME = 0x04000; + public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; + public static final int FORMAT_ABBREV_MONTH = 0x10000; + public static final int FORMAT_NUMERIC_DATE = 0x20000; + public static final int FORMAT_ABBREV_RELATIVE = 0x40000; + public static final int FORMAT_ABBREV_ALL = 0x80000; + + /** + * Creates an immutable ICU timezone backed by the specified libcore timezone data. At the time of + * writing the libcore implementation is faster but restricted to 1902 - 2038. + * Callers must not modify the {@code tz} after calling this method. + */ + public static com.ibm.icu.util.TimeZone icuTimeZone(java.util.TimeZone tz) { + JavaTimeZone javaTimeZone = new JavaTimeZone(tz, null); + javaTimeZone.freeze(); // Optimization - allows the timezone to be copied cheaply. + return javaTimeZone; + } + + public static Calendar createIcuCalendar(com.ibm.icu.util.TimeZone icuTimeZone, ULocale icuLocale, + long timeInMillis) { + Calendar calendar = new GregorianCalendar(icuTimeZone, icuLocale); + calendar.setTimeInMillis(timeInMillis); + return calendar; + } + + public static String toSkeleton(Calendar calendar, int flags) { + return toSkeleton(calendar, calendar, flags); + } + + public static String toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags) { + if ((flags & FORMAT_ABBREV_ALL) != 0) { + flags |= FORMAT_ABBREV_MONTH | FORMAT_ABBREV_TIME | FORMAT_ABBREV_WEEKDAY; + } + + String monthPart = "MMMM"; + if ((flags & FORMAT_NUMERIC_DATE) != 0) { + monthPart = "M"; + } else if ((flags & FORMAT_ABBREV_MONTH) != 0) { + monthPart = "MMM"; + } + + String weekPart = "EEEE"; + if ((flags & FORMAT_ABBREV_WEEKDAY) != 0) { + weekPart = "EEE"; + } + + String timePart = "j"; // "j" means choose 12 or 24 hour based on current locale. + if ((flags & FORMAT_24HOUR) != 0) { + timePart = "H"; + } else if ((flags & FORMAT_12HOUR) != 0) { + timePart = "h"; + } + + // If we've not been asked to abbreviate times, or we're using the 24-hour clock (where it + // never makes sense to leave out the minutes), include minutes. This gets us times like + // "4 PM" while avoiding times like "16" (for "16:00"). + if ((flags & FORMAT_ABBREV_TIME) == 0 || (flags & FORMAT_24HOUR) != 0) { + timePart += "m"; + } else { + // Otherwise, we're abbreviating a 12-hour time, and should only show the minutes + // if they're not both "00". + if (!(onTheHour(startCalendar) && onTheHour(endCalendar))) { + timePart = timePart + "m"; + } + } + + if (fallOnDifferentDates(startCalendar, endCalendar)) { + flags |= FORMAT_SHOW_DATE; + } + + if (fallInSameMonth(startCalendar, endCalendar) && (flags & FORMAT_NO_MONTH_DAY) != 0) { + flags &= (~FORMAT_SHOW_WEEKDAY); + flags &= (~FORMAT_SHOW_TIME); + } + + if ((flags & (FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_WEEKDAY)) == 0) { + flags |= FORMAT_SHOW_DATE; + } + + // If we've been asked to show the date, work out whether we think we should show the year. + if ((flags & FORMAT_SHOW_DATE) != 0) { + if ((flags & FORMAT_SHOW_YEAR) != 0) { + // The caller explicitly wants us to show the year. + } else if ((flags & FORMAT_NO_YEAR) != 0) { + // The caller explicitly doesn't want us to show the year, even if we otherwise would. + } else if (!fallInSameYear(startCalendar, endCalendar) || !isThisYear(startCalendar)) { + flags |= FORMAT_SHOW_YEAR; + } + } + + StringBuilder builder = new StringBuilder(); + if ((flags & (FORMAT_SHOW_DATE | FORMAT_NO_MONTH_DAY)) != 0) { + if ((flags & FORMAT_SHOW_YEAR) != 0) { + builder.append("y"); + } + builder.append(monthPart); + if ((flags & FORMAT_NO_MONTH_DAY) == 0) { + builder.append("d"); + } + } + if ((flags & FORMAT_SHOW_WEEKDAY) != 0) { + builder.append(weekPart); + } + if ((flags & FORMAT_SHOW_TIME) != 0) { + builder.append(timePart); + } + return builder.toString(); + } + + public static int dayDistance(Calendar c1, Calendar c2) { + return c2.get(Calendar.JULIAN_DAY) - c1.get(Calendar.JULIAN_DAY); + } + + private static boolean onTheHour(Calendar c) { + return c.get(Calendar.MINUTE) == 0 && c.get(Calendar.SECOND) == 0; + } + + private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) { + return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) || + c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) || + c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH); + } + + private static boolean fallInSameMonth(Calendar c1, Calendar c2) { + return c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH); + } + + private static boolean fallInSameYear(Calendar c1, Calendar c2) { + return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR); + } + + private static boolean isThisYear(Calendar c) { + Calendar now = (Calendar) c.clone(); + now.setTimeInMillis(System.currentTimeMillis()); + return c.get(Calendar.YEAR) == now.get(Calendar.YEAR); + } +} diff --git a/luni/src/main/java/libcore/icu/ICU.java b/luni/src/main/java/libcore/icu/ICU.java index 0ef3f93..42def54 100644 --- a/luni/src/main/java/libcore/icu/ICU.java +++ b/luni/src/main/java/libcore/icu/ICU.java @@ -457,4 +457,7 @@ public final class ICU { * Returns a locale name, not a BCP-47 language tag. e.g. en_US not en-US. */ public static native String getDefaultLocale(); + + /** Returns the TZData version as reported by ICU4C. */ + public static native String getTZDataVersion(); } diff --git a/luni/src/main/java/libcore/icu/LocaleData.java b/luni/src/main/java/libcore/icu/LocaleData.java index cca38e1..cf52b9c 100644 --- a/luni/src/main/java/libcore/icu/LocaleData.java +++ b/luni/src/main/java/libcore/icu/LocaleData.java @@ -88,15 +88,12 @@ public final class LocaleData { public String shortDateFormat4; // Used by DateFormat to implement 12- and 24-hour SHORT and MEDIUM. + // The first two are also used directly by frameworks code. public String timeFormat_hm; public String timeFormat_Hm; public String timeFormat_hms; public String timeFormat_Hms; - // Used by android.text.format.DateFormat.getTimeFormat. - public String timeFormat12; // "hh:mm a" - public String timeFormat24; // "HH:mm" - // Used by DecimalFormatSymbols. public char zeroDigit; public char decimalSeparator; @@ -213,12 +210,6 @@ public final class LocaleData { localeData.timeFormat_Hm = ICU.getBestDateTimePattern("Hm", locale); localeData.timeFormat_hms = ICU.getBestDateTimePattern("hms", locale); localeData.timeFormat_Hms = ICU.getBestDateTimePattern("Hms", locale); - // We could move callers over to the other fields, but these seem simpler and discourage - // people from shooting themselves in the foot by learning about patterns and skeletons. - // TODO: the right fix here is probably to move callers over to java.text.DateFormat, - // so nothing outside libcore references these any more. - localeData.timeFormat12 = localeData.timeFormat_hm; - localeData.timeFormat24 = localeData.timeFormat_Hm; // Fix up a couple of patterns. if (localeData.fullTimeFormat != null) { diff --git a/luni/src/main/java/libcore/icu/NativeBreakIterator.java b/luni/src/main/java/libcore/icu/NativeBreakIterator.java deleted file mode 100644 index 992aac2..0000000 --- a/luni/src/main/java/libcore/icu/NativeBreakIterator.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2008 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.icu; - -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; -import java.util.Locale; - -public final class NativeBreakIterator implements Cloneable { - // Acceptable values for the 'type' field. - private static final int BI_CHAR_INSTANCE = 1; - private static final int BI_WORD_INSTANCE = 2; - private static final int BI_LINE_INSTANCE = 3; - private static final int BI_SENT_INSTANCE = 4; - - // The address of the native peer. - // Uses of this must be manually synchronized to avoid native crashes. - private final long address; - - private final int type; - private String string; - private CharacterIterator charIterator; - - private NativeBreakIterator(long address, int type) { - this.address = address; - this.type = type; - this.charIterator = new StringCharacterIterator(""); - } - - @Override - public Object clone() { - long cloneAddr = cloneImpl(this.address); - NativeBreakIterator clone = new NativeBreakIterator(cloneAddr, this.type); - clone.string = this.string; - // The RI doesn't clone the CharacterIterator. - clone.charIterator = this.charIterator; - return clone; - } - - @Override - public boolean equals(Object object) { - if (object == this) { - return true; - } - if (!(object instanceof NativeBreakIterator)) { - return false; - } - // TODO: is this sufficient? shouldn't we be checking the underlying rules? - NativeBreakIterator rhs = (NativeBreakIterator) object; - return type == rhs.type && charIterator.equals(rhs.charIterator); - } - - @Override - public int hashCode() { - return 42; // No-one uses BreakIterator as a hash key. - } - - @Override protected void finalize() throws Throwable { - try { - closeImpl(this.address); - } finally { - super.finalize(); - } - } - - public int current() { - return currentImpl(this.address, this.string); - } - - public int first() { - return firstImpl(this.address, this.string); - } - - public int following(int offset) { - return followingImpl(this.address, this.string, offset); - } - - public CharacterIterator getText() { - int newLocation = currentImpl(this.address, this.string); - this.charIterator.setIndex(newLocation); - return this.charIterator; - } - - public int last() { - return lastImpl(this.address, this.string); - } - - public int next(int n) { - return nextImpl(this.address, this.string, n); - } - - public int next() { - return nextImpl(this.address, this.string, 1); - } - - public int previous() { - return previousImpl(this.address, this.string); - } - - public void setText(CharacterIterator newText) { - StringBuilder sb = new StringBuilder(); - for (char c = newText.first(); c != CharacterIterator.DONE; c = newText.next()) { - sb.append(c); - } - setText(sb.toString(), newText); - } - - public void setText(String newText) { - setText(newText, new StringCharacterIterator(newText)); - } - - private void setText(String s, CharacterIterator it) { - this.string = s; - this.charIterator = it; - setTextImpl(this.address, this.string); - } - - public boolean hasText() { - return (string != null); - } - - public boolean isBoundary(int offset) { - return isBoundaryImpl(this.address, this.string, offset); - } - - public int preceding(int offset) { - return precedingImpl(this.address, this.string, offset); - } - - public static NativeBreakIterator getCharacterInstance(Locale locale) { - return new NativeBreakIterator(getCharacterInstanceImpl(locale.toLanguageTag()), BI_CHAR_INSTANCE); - } - - public static NativeBreakIterator getLineInstance(Locale locale) { - return new NativeBreakIterator(getLineInstanceImpl(locale.toLanguageTag()), BI_LINE_INSTANCE); - } - - public static NativeBreakIterator getSentenceInstance(Locale locale) { - return new NativeBreakIterator(getSentenceInstanceImpl(locale.toLanguageTag()), BI_SENT_INSTANCE); - } - - public static NativeBreakIterator getWordInstance(Locale locale) { - return new NativeBreakIterator(getWordInstanceImpl(locale.toLanguageTag()), BI_WORD_INSTANCE); - } - - private static native long getCharacterInstanceImpl(String locale); - private static native long getWordInstanceImpl(String locale); - private static native long getLineInstanceImpl(String locale); - private static native long getSentenceInstanceImpl(String locale); - private static synchronized native long cloneImpl(long address); - - private static synchronized native void closeImpl(long address); - - private static synchronized native void setTextImpl(long address, String text); - private static synchronized native int precedingImpl(long address, String text, int offset); - private static synchronized native boolean isBoundaryImpl(long address, String text, int offset); - private static synchronized native int nextImpl(long address, String text, int n); - private static synchronized native int previousImpl(long address, String text); - private static synchronized native int currentImpl(long address, String text); - private static synchronized native int firstImpl(long address, String text); - private static synchronized native int followingImpl(long address, String text, int offset); - private static synchronized native int lastImpl(long address, String text); -} diff --git a/luni/src/main/java/libcore/icu/RelativeDateTimeFormatter.java b/luni/src/main/java/libcore/icu/RelativeDateTimeFormatter.java new file mode 100644 index 0000000..e2afa61 --- /dev/null +++ b/luni/src/main/java/libcore/icu/RelativeDateTimeFormatter.java @@ -0,0 +1,360 @@ +/* + * 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.icu; + +import java.util.Locale; +import libcore.util.BasicLruCache; + +import com.ibm.icu.text.DisplayContext; +import com.ibm.icu.util.Calendar; +import com.ibm.icu.util.ULocale; + +import static libcore.icu.DateUtilsBridge.FORMAT_ABBREV_ALL; +import static libcore.icu.DateUtilsBridge.FORMAT_ABBREV_MONTH; +import static libcore.icu.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; +import static libcore.icu.DateUtilsBridge.FORMAT_NO_YEAR; +import static libcore.icu.DateUtilsBridge.FORMAT_NUMERIC_DATE; +import static libcore.icu.DateUtilsBridge.FORMAT_SHOW_DATE; +import static libcore.icu.DateUtilsBridge.FORMAT_SHOW_TIME; +import static libcore.icu.DateUtilsBridge.FORMAT_SHOW_YEAR; + +/** + * Exposes icu4j's RelativeDateTimeFormatter. + */ +public final class RelativeDateTimeFormatter { + + public static final long SECOND_IN_MILLIS = 1000; + public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; + public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; + public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; + public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; + // YEAR_IN_MILLIS considers 364 days as a year. However, since this + // constant comes from public API in DateUtils, it cannot be fixed here. + public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52; + + private static final int DAY_IN_MS = 24 * 60 * 60 * 1000; + private static final int EPOCH_JULIAN_DAY = 2440588; + + private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); + + static class FormatterCache + extends BasicLruCache<String, com.ibm.icu.text.RelativeDateTimeFormatter> { + FormatterCache() { + super(8); + } + } + + private RelativeDateTimeFormatter() { + } + + /** + * This is the internal API that implements the functionality of + * DateUtils.getRelativeTimeSpanString(long, long, long, int), which is to + * return a string describing 'time' as a time relative to 'now' such as + * '5 minutes ago', or 'in 2 days'. More examples can be found in DateUtils' + * doc. + * + * In the implementation below, it selects the appropriate time unit based on + * the elapsed time between time' and 'now', e.g. minutes, days and etc. + * Callers may also specify the desired minimum resolution to show in the + * result. For example, '45 minutes ago' will become '0 hours ago' when + * minResolution is HOUR_IN_MILLIS. Once getting the quantity and unit to + * display, it calls icu4j's RelativeDateTimeFormatter to format the actual + * string according to the given locale. + * + * Note that when minResolution is set to DAY_IN_MILLIS, it returns the + * result depending on the actual date difference. For example, it will + * return 'Yesterday' even if 'time' was less than 24 hours ago but falling + * onto a different calendar day. + * + * It takes two additional parameters of Locale and TimeZone than the + * DateUtils' API. Caller must specify the locale and timezone. + * FORMAT_ABBREV_RELATIVE or FORMAT_ABBREV_ALL can be set in 'flags' to get + * the abbreviated forms when available. When 'time' equals to 'now', it + * always // returns a string like '0 seconds/minutes/... ago' according to + * minResolution. + */ + public static String getRelativeTimeSpanString(Locale locale, java.util.TimeZone tz, long time, + long now, long minResolution, int flags) { + // Android has been inconsistent about capitalization in the past. e.g. bug http://b/20247811. + // Now we capitalize everything consistently. + final DisplayContext displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE; + return getRelativeTimeSpanString(locale, tz, time, now, minResolution, flags, displayContext); + } + + public static String getRelativeTimeSpanString(Locale locale, java.util.TimeZone tz, long time, + long now, long minResolution, int flags, DisplayContext displayContext) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + if (tz == null) { + throw new NullPointerException("tz == null"); + } + ULocale icuLocale = ULocale.forLocale(locale); + com.ibm.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); + return getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution, flags, + displayContext); + } + + private static String getRelativeTimeSpanString(ULocale icuLocale, + com.ibm.icu.util.TimeZone icuTimeZone, long time, long now, long minResolution, int flags, + DisplayContext displayContext) { + + long duration = Math.abs(now - time); + boolean past = (now >= time); + + com.ibm.icu.text.RelativeDateTimeFormatter.Style style; + if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) { + style = com.ibm.icu.text.RelativeDateTimeFormatter.Style.SHORT; + } else { + style = com.ibm.icu.text.RelativeDateTimeFormatter.Style.LONG; + } + + com.ibm.icu.text.RelativeDateTimeFormatter.Direction direction; + if (past) { + direction = com.ibm.icu.text.RelativeDateTimeFormatter.Direction.LAST; + } else { + direction = com.ibm.icu.text.RelativeDateTimeFormatter.Direction.NEXT; + } + + // 'relative' defaults to true as we are generating relative time span + // string. It will be set to false when we try to display strings without + // a quantity, such as 'Yesterday', etc. + boolean relative = true; + int count; + com.ibm.icu.text.RelativeDateTimeFormatter.RelativeUnit unit; + com.ibm.icu.text.RelativeDateTimeFormatter.AbsoluteUnit aunit = null; + + if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { + count = (int)(duration / SECOND_IN_MILLIS); + unit = com.ibm.icu.text.RelativeDateTimeFormatter.RelativeUnit.SECONDS; + } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { + count = (int)(duration / MINUTE_IN_MILLIS); + unit = com.ibm.icu.text.RelativeDateTimeFormatter.RelativeUnit.MINUTES; + } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) { + // Even if 'time' actually happened yesterday, we don't format it as + // "Yesterday" in this case. Unless the duration is longer than a day, + // or minResolution is specified as DAY_IN_MILLIS by user. + count = (int)(duration / HOUR_IN_MILLIS); + unit = com.ibm.icu.text.RelativeDateTimeFormatter.RelativeUnit.HOURS; + } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) { + count = Math.abs(dayDistance(icuTimeZone, time, now)); + unit = com.ibm.icu.text.RelativeDateTimeFormatter.RelativeUnit.DAYS; + + if (count == 2) { + // Some locales have special terms for "2 days ago". Return them if + // available. Note that we cannot set up direction and unit here and + // make it fall through to use the call near the end of the function, + // because for locales that don't have special terms for "2 days ago", + // icu4j returns an empty string instead of falling back to strings + // like "2 days ago". + String str; + if (past) { + synchronized (CACHED_FORMATTERS) { + str = getFormatter(icuLocale, style, displayContext) + .format( + com.ibm.icu.text.RelativeDateTimeFormatter.Direction.LAST_2, + com.ibm.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY); + } + } else { + synchronized (CACHED_FORMATTERS) { + str = getFormatter(icuLocale, style, displayContext) + .format( + com.ibm.icu.text.RelativeDateTimeFormatter.Direction.NEXT_2, + com.ibm.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY); + } + } + if (str != null && !str.isEmpty()) { + return str; + } + // Fall back to show something like "2 days ago". + } else if (count == 1) { + // Show "Yesterday / Tomorrow" instead of "1 day ago / In 1 day". + aunit = com.ibm.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY; + relative = false; + } else if (count == 0) { + // Show "Today" if time and now are on the same day. + aunit = com.ibm.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY; + direction = com.ibm.icu.text.RelativeDateTimeFormatter.Direction.THIS; + relative = false; + } + } else if (minResolution == WEEK_IN_MILLIS) { + count = (int)(duration / WEEK_IN_MILLIS); + unit = com.ibm.icu.text.RelativeDateTimeFormatter.RelativeUnit.WEEKS; + } else { + Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time); + // The duration is longer than a week and minResolution is not + // WEEK_IN_MILLIS. Return the absolute date instead of relative time. + + // Bug 19822016: + // If user doesn't supply the year display flag, we need to explicitly + // set that to show / hide the year based on time and now. Otherwise + // formatDateRange() would determine that based on the current system + // time and may give wrong results. + if ((flags & (FORMAT_NO_YEAR | FORMAT_SHOW_YEAR)) == 0) { + Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, now); + + if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) { + flags |= FORMAT_SHOW_YEAR; + } else { + flags |= FORMAT_NO_YEAR; + } + } + return DateTimeFormat.format(icuLocale, timeCalendar, flags, displayContext); + } + + synchronized (CACHED_FORMATTERS) { + com.ibm.icu.text.RelativeDateTimeFormatter formatter = + getFormatter(icuLocale, style, displayContext); + if (relative) { + return formatter.format(count, direction, unit); + } else { + return formatter.format(direction, aunit); + } + } + } + + /** + * This is the internal API that implements + * DateUtils.getRelativeDateTimeString(long, long, long, long, int), which is + * to return a string describing 'time' as a time relative to 'now', formatted + * like '[relative time/date], [time]'. More examples can be found in + * DateUtils' doc. + * + * The function is similar to getRelativeTimeSpanString, but it always + * appends the absolute time to the relative time string to return + * '[relative time/date clause], [absolute time clause]'. It also takes an + * extra parameter transitionResolution to determine the format of the date + * clause. When the elapsed time is less than the transition resolution, it + * displays the relative time string. Otherwise, it gives the absolute + * numeric date string as the date clause. With the date and time clauses, it + * relies on icu4j's RelativeDateTimeFormatter::combineDateAndTime() to + * concatenate the two. + * + * It takes two additional parameters of Locale and TimeZone than the + * DateUtils' API. Caller must specify the locale and timezone. + * FORMAT_ABBREV_RELATIVE or FORMAT_ABBREV_ALL can be set in 'flags' to get + * the abbreviated forms when they are available. + * + * Bug 5252772: Since the absolute time will always be part of the result, + * minResolution will be set to at least DAY_IN_MILLIS to correctly indicate + * the date difference. For example, when it's 1:30 AM, it will return + * 'Yesterday, 11:30 PM' for getRelativeDateTimeString(null, null, + * now - 2 hours, now, HOUR_IN_MILLIS, DAY_IN_MILLIS, 0), instead of '2 + * hours ago, 11:30 PM' even with minResolution being HOUR_IN_MILLIS. + */ + public static String getRelativeDateTimeString(Locale locale, java.util.TimeZone tz, long time, + long now, long minResolution, long transitionResolution, int flags) { + + if (locale == null) { + throw new NullPointerException("locale == null"); + } + if (tz == null) { + throw new NullPointerException("tz == null"); + } + ULocale icuLocale = ULocale.forLocale(locale); + com.ibm.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); + + long duration = Math.abs(now - time); + // It doesn't make much sense to have results like: "1 week ago, 10:50 AM". + if (transitionResolution > WEEK_IN_MILLIS) { + transitionResolution = WEEK_IN_MILLIS; + } + com.ibm.icu.text.RelativeDateTimeFormatter.Style style; + if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) { + style = com.ibm.icu.text.RelativeDateTimeFormatter.Style.SHORT; + } else { + style = com.ibm.icu.text.RelativeDateTimeFormatter.Style.LONG; + } + + Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time); + Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, now); + + int days = Math.abs(DateUtilsBridge.dayDistance(timeCalendar, nowCalendar)); + + // Now get the date clause, either in relative format or the actual date. + String dateClause; + if (duration < transitionResolution) { + // This is to fix bug 5252772. If there is any date difference, we should + // promote the minResolution to DAY_IN_MILLIS so that it can display the + // date instead of "x hours/minutes ago, [time]". + if (days > 0 && minResolution < DAY_IN_MILLIS) { + minResolution = DAY_IN_MILLIS; + } + dateClause = getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution, + flags, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); + } else { + // We always use fixed flags to format the date clause. User-supplied + // flags are ignored. + if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) { + // Different years + flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE; + } else { + // Default + flags = FORMAT_SHOW_DATE | FORMAT_NO_YEAR | FORMAT_ABBREV_MONTH; + } + + dateClause = DateTimeFormat.format(icuLocale, timeCalendar, flags, + DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); + } + + String timeClause = DateTimeFormat.format(icuLocale, timeCalendar, FORMAT_SHOW_TIME, + DisplayContext.CAPITALIZATION_NONE); + + // icu4j also has other options available to control the capitalization. We are currently using + // the _NONE option only. + DisplayContext capitalizationContext = DisplayContext.CAPITALIZATION_NONE; + + // Combine the two clauses, such as '5 days ago, 10:50 AM'. + synchronized (CACHED_FORMATTERS) { + return getFormatter(icuLocale, style, capitalizationContext) + .combineDateAndTime(dateClause, timeClause); + } + } + + /** + * getFormatter() caches the RelativeDateTimeFormatter instances based on + * the combination of localeName, sytle and capitalizationContext. It + * should always be used along with the action of the formatter in a + * synchronized block, because otherwise the formatter returned by + * getFormatter() may have been evicted by the time of the call to + * formatter->action(). + */ + private static com.ibm.icu.text.RelativeDateTimeFormatter getFormatter( + ULocale locale, com.ibm.icu.text.RelativeDateTimeFormatter.Style style, + DisplayContext displayContext) { + String key = locale + "\t" + style + "\t" + displayContext; + com.ibm.icu.text.RelativeDateTimeFormatter formatter = CACHED_FORMATTERS.get(key); + if (formatter == null) { + formatter = com.ibm.icu.text.RelativeDateTimeFormatter.getInstance( + locale, null, style, displayContext); + CACHED_FORMATTERS.put(key, formatter); + } + return formatter; + } + + // Return the date difference for the two times in a given timezone. + private static int dayDistance(com.ibm.icu.util.TimeZone icuTimeZone, long startTime, + long endTime) { + return julianDay(icuTimeZone, endTime) - julianDay(icuTimeZone, startTime); + } + + private static int julianDay(com.ibm.icu.util.TimeZone icuTimeZone, long time) { + long utcMs = time + icuTimeZone.getOffset(time); + return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY; + } +} diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java index b3dc74b..532493a 100644 --- a/luni/src/main/java/libcore/io/BlockGuardOs.java +++ b/luni/src/main/java/libcore/io/BlockGuardOs.java @@ -83,7 +83,7 @@ public class BlockGuardOs extends ForwardingOs { // The usual case is that this _isn't_ a socket, so the getsockopt(2) call in // isLingerSocket will throw, and that's really expensive. Try to avoid asking // if we don't care. - if (fd.isSocket()) { + if (fd.isSocket$()) { if (isLingerSocket(fd)) { // If the fd is a socket with SO_LINGER set, we might block indefinitely. // We allow non-linger sockets so that apps can close their network diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java index 584fd58..5c0c8fd 100644 --- a/luni/src/main/java/libcore/io/ForwardingOs.java +++ b/luni/src/main/java/libcore/io/ForwardingOs.java @@ -54,10 +54,12 @@ public class ForwardingOs implements Os { public boolean access(String path, int mode) throws ErrnoException { return os.access(path, mode); } public InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException { return os.android_getaddrinfo(node, hints, netId); } public void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { os.bind(fd, address, port); } + public void bind(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { os.bind(fd, address); } public void chmod(String path, int mode) throws ErrnoException { os.chmod(path, mode); } public void chown(String path, int uid, int gid) throws ErrnoException { os.chown(path, uid, gid); } public void close(FileDescriptor fd) throws ErrnoException { os.close(fd); } public void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { os.connect(fd, address, port); } + public void connect(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException { os.connect(fd, address); } public FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return os.dup(oldFd); } public FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return os.dup2(oldFd, newFd); } public String[] environ() { return os.environ(); } @@ -65,9 +67,9 @@ public class ForwardingOs implements Os { public void execve(String filename, String[] argv, String[] envp) throws ErrnoException { os.execve(filename, argv, envp); } public void fchmod(FileDescriptor fd, int mode) throws ErrnoException { os.fchmod(fd, mode); } public void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { os.fchown(fd, uid, gid); } - public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return os.fcntlVoid(fd, cmd); } - public int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException { return os.fcntlLong(fd, cmd, arg); } public int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException, InterruptedIOException { return os.fcntlFlock(fd, cmd, arg); } + public int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException { return os.fcntlInt(fd, cmd, arg); } + public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException { return os.fcntlVoid(fd, cmd); } public void fdatasync(FileDescriptor fd) throws ErrnoException { os.fdatasync(fd); } public StructStat fstat(FileDescriptor fd) throws ErrnoException { return os.fstat(fd); } public StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { return os.fstatvfs(fd); } @@ -80,6 +82,7 @@ public class ForwardingOs implements Os { public String getenv(String name) { return os.getenv(name); } public String getnameinfo(InetAddress address, int flags) throws GaiException { return os.getnameinfo(address, flags); } public SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return os.getpeername(fd); } + public int getpgid(int pid) throws ErrnoException { return os.getpgid(pid); } public int getpid() { return os.getpid(); } public int getppid() { return os.getppid(); } public StructPasswd getpwnam(String name) throws ErrnoException { return os.getpwnam(name); } @@ -113,7 +116,7 @@ public class ForwardingOs implements Os { public void munlock(long address, long byteCount) throws ErrnoException { os.munlock(address, byteCount); } public void munmap(long address, long byteCount) throws ErrnoException { os.munmap(address, byteCount); } public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return os.open(path, flags, mode); } - public FileDescriptor[] pipe() throws ErrnoException { return os.pipe(); } + public FileDescriptor[] pipe2(int flags) throws ErrnoException { return os.pipe2(flags); } public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return os.poll(fds, timeoutMs); } public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { os.posix_fallocate(fd, offset, length); } public int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException { return os.prctl(option, arg2, arg3, arg4, arg5); }; @@ -132,10 +135,14 @@ public class ForwardingOs implements Os { public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return os.sendfile(outFd, inFd, inOffset, byteCount); } public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return os.sendto(fd, buffer, flags, inetAddress, port); } public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); } + public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException { return os.sendto(fd, bytes, byteOffset, byteCount, flags, address); } public void setegid(int egid) throws ErrnoException { os.setegid(egid); } public void setenv(String name, String value, boolean overwrite) throws ErrnoException { os.setenv(name, value, overwrite); } public void seteuid(int euid) throws ErrnoException { os.seteuid(euid); } public void setgid(int gid) throws ErrnoException { os.setgid(gid); } + public void setpgid(int pid, int pgid) throws ErrnoException { os.setpgid(pid, pgid); } + public void setregid(int rgid, int egid) throws ErrnoException { os.setregid(rgid, egid); } + public void setreuid(int ruid, int euid) throws ErrnoException { os.setregid(ruid, euid); } public int setsid() throws ErrnoException { return os.setsid(); } public void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptByte(fd, level, option, value); } public void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { os.setsockoptIfreq(fd, level, option, value); } diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java index acc8d4f..fcb30dd 100644 --- a/luni/src/main/java/libcore/io/IoBridge.java +++ b/luni/src/main/java/libcore/io/IoBridge.java @@ -225,11 +225,7 @@ public final class IoBridge { if (!fd.valid()) { throw new SocketException("Socket closed"); } - if (errnoException.errno == EINTR) { - return false; // Punt and ask the caller to try again. - } else { - cause = errnoException; - } + cause = errnoException; } String detail = connectDetail(inetAddress, port, timeoutMs, cause); if (cause.errno == ETIMEDOUT) { diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java index 5a19f17..b01759d 100644 --- a/luni/src/main/java/libcore/io/IoUtils.java +++ b/luni/src/main/java/libcore/io/IoUtils.java @@ -30,8 +30,6 @@ import java.util.Random; import static android.system.OsConstants.*; public final class IoUtils { - private static final Random TEMPORARY_DIRECTORY_PRNG = new Random(); - private IoUtils() { } @@ -96,7 +94,7 @@ public final class IoUtils { } else { flags &= ~O_NONBLOCK; } - Libcore.os.fcntlLong(fd, F_SETFL, flags); + Libcore.os.fcntlInt(fd, F_SETFL, flags); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } @@ -142,7 +140,7 @@ public final class IoUtils { */ public static File createTemporaryDirectory(String prefix) { while (true) { - String candidateName = prefix + TEMPORARY_DIRECTORY_PRNG.nextInt(); + String candidateName = prefix + Math.randomIntInternal(); File result = new File(System.getProperty("java.io.tmpdir"), candidateName); if (result.mkdir()) { return result; diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java index 9f080a6..987d331 100644 --- a/luni/src/main/java/libcore/io/Os.java +++ b/luni/src/main/java/libcore/io/Os.java @@ -45,10 +45,12 @@ public interface Os { public boolean access(String path, int mode) throws ErrnoException; public InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException; public void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException; + public void bind(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException; public void chmod(String path, int mode) throws ErrnoException; public void chown(String path, int uid, int gid) throws ErrnoException; public void close(FileDescriptor fd) throws ErrnoException; public void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException; + public void connect(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException; public FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException; public FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException; public String[] environ(); @@ -56,9 +58,9 @@ public interface Os { public void execve(String filename, String[] argv, String[] envp) throws ErrnoException; public void fchmod(FileDescriptor fd, int mode) throws ErrnoException; public void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException; - public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException; - public int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException; public int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException, InterruptedIOException; + public int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException; + public int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException; public void fdatasync(FileDescriptor fd) throws ErrnoException; public StructStat fstat(FileDescriptor fd) throws ErrnoException; public StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException; @@ -72,6 +74,7 @@ public interface Os { /* TODO: break into getnameinfoHost and getnameinfoService? */ public String getnameinfo(InetAddress address, int flags) throws GaiException; public SocketAddress getpeername(FileDescriptor fd) throws ErrnoException; + public int getpgid(int pid) throws ErrnoException; public int getpid(); public int getppid(); public StructPasswd getpwnam(String name) throws ErrnoException; @@ -105,7 +108,7 @@ public interface Os { public void munlock(long address, long byteCount) throws ErrnoException; public void munmap(long address, long byteCount) throws ErrnoException; public FileDescriptor open(String path, int flags, int mode) throws ErrnoException; - public FileDescriptor[] pipe() throws ErrnoException; + public FileDescriptor[] pipe2(int flags) throws ErrnoException; /* TODO: if we used the non-standard ppoll(2) behind the scenes, we could take a long timeout. */ public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException; public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException; @@ -124,11 +127,15 @@ public interface Os { public void rename(String oldPath, String newPath) throws ErrnoException; public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException; public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException; + public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException; public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException; public void setegid(int egid) throws ErrnoException; public void setenv(String name, String value, boolean overwrite) throws ErrnoException; public void seteuid(int euid) throws ErrnoException; public void setgid(int gid) throws ErrnoException; + public void setpgid(int pid, int pgid) throws ErrnoException; + public void setregid(int rgid, int egid) throws ErrnoException; + public void setreuid(int ruid, int euid) throws ErrnoException; public int setsid() throws ErrnoException; public void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException; public void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException; diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java index 5bd1b06..d680200 100644 --- a/luni/src/main/java/libcore/io/Posix.java +++ b/luni/src/main/java/libcore/io/Posix.java @@ -48,10 +48,12 @@ public final class Posix implements Os { public native boolean access(String path, int mode) throws ErrnoException; public native InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException; public native void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException; + public native void bind(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException; public native void chmod(String path, int mode) throws ErrnoException; public native void chown(String path, int uid, int gid) throws ErrnoException; public native void close(FileDescriptor fd) throws ErrnoException; public native void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException; + public native void connect(FileDescriptor fd, SocketAddress address) throws ErrnoException, SocketException; public native FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException; public native FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException; public native String[] environ(); @@ -59,9 +61,9 @@ public final class Posix implements Os { public native void execve(String filename, String[] argv, String[] envp) throws ErrnoException; public native void fchmod(FileDescriptor fd, int mode) throws ErrnoException; public native void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException; - public native int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException; - public native int fcntlLong(FileDescriptor fd, int cmd, long arg) throws ErrnoException; public native int fcntlFlock(FileDescriptor fd, int cmd, StructFlock arg) throws ErrnoException, InterruptedIOException; + public native int fcntlInt(FileDescriptor fd, int cmd, int arg) throws ErrnoException; + public native int fcntlVoid(FileDescriptor fd, int cmd) throws ErrnoException; public native void fdatasync(FileDescriptor fd) throws ErrnoException; public native StructStat fstat(FileDescriptor fd) throws ErrnoException; public native StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException; @@ -74,6 +76,7 @@ public final class Posix implements Os { public native String getenv(String name); public native String getnameinfo(InetAddress address, int flags) throws GaiException; public native SocketAddress getpeername(FileDescriptor fd) throws ErrnoException; + public native int getpgid(int pid); public native int getpid(); public native int getppid(); public native StructPasswd getpwnam(String name) throws ErrnoException; @@ -107,7 +110,7 @@ public final class Posix implements Os { public native void munlock(long address, long byteCount) throws ErrnoException; public native void munmap(long address, long byteCount) throws ErrnoException; public native FileDescriptor open(String path, int flags, int mode) throws ErrnoException; - public native FileDescriptor[] pipe() throws ErrnoException; + public native FileDescriptor[] pipe2(int flags) throws ErrnoException; public native int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException; public native void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException; public native int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException; @@ -205,11 +208,18 @@ public final class Posix implements Os { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return sendtoBytes(fd, bytes, byteOffset, byteCount, flags, inetAddress, port); } + public int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException { + return sendtoBytes(fd, bytes, byteOffset, byteCount, flags, address); + } private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException; + private native int sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, SocketAddress address) throws ErrnoException, SocketException; public native void setegid(int egid) throws ErrnoException; public native void setenv(String name, String value, boolean overwrite) throws ErrnoException; public native void seteuid(int euid) throws ErrnoException; public native void setgid(int gid) throws ErrnoException; + public native void setpgid(int pid, int pgid) throws ErrnoException; + public native void setregid(int rgid, int egid) throws ErrnoException; + public native void setreuid(int ruid, int euid) throws ErrnoException; public native int setsid() throws ErrnoException; public native void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException; public native void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException; diff --git a/luni/src/main/java/libcore/net/MimeUtils.java b/luni/src/main/java/libcore/net/MimeUtils.java index 125ea87..e36b3d1 100644 --- a/luni/src/main/java/libcore/net/MimeUtils.java +++ b/luni/src/main/java/libcore/net/MimeUtils.java @@ -16,13 +16,8 @@ package libcore.net; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; import java.util.HashMap; import java.util.Map; -import java.util.Properties; /** * Utilities for dealing with MIME types. @@ -49,6 +44,7 @@ public final class MimeUtils { add("application/andrew-inset", "ez"); add("application/dsptype", "tsp"); + add("application/epub+zip", "epub"); add("application/hta", "hta"); add("application/mac-binhex40", "hqx"); add("application/mathematica", "nb"); @@ -73,6 +69,8 @@ public final class MimeUtils { add("application/vnd.oasis.opendocument.graphics", "odg"); add("application/vnd.oasis.opendocument.graphics-template", "otg"); add("application/vnd.oasis.opendocument.image", "odi"); + add("application/vnd.oasis.opendocument.presentation", "odp"); + add("application/vnd.oasis.opendocument.presentation-template", "otp"); add("application/vnd.oasis.opendocument.spreadsheet", "ods"); add("application/vnd.oasis.opendocument.spreadsheet-template", "ots"); add("application/vnd.oasis.opendocument.text", "odt"); @@ -148,6 +146,7 @@ public final class MimeUtils { add("application/x-gtar", "gtar"); add("application/x-gtar", "taz"); add("application/x-hdf", "hdf"); + add("application/x-hwp", "hwp"); // http://b/18788282. add("application/x-ica", "ica"); add("application/x-internet-signup", "ins"); add("application/x-internet-signup", "isp"); @@ -378,7 +377,6 @@ public final class MimeUtils { add("video/x-webex", "wrf"); add("x-conference/x-cooltalk", "ice"); add("x-epoc/x-sisx-app", "sisx"); - applyOverrides(); } private static void add(String mimeType, String extension) { @@ -395,61 +393,6 @@ public final class MimeUtils { } } - private static InputStream getContentTypesPropertiesStream() { - // User override? - String userTable = System.getProperty("content.types.user.table"); - if (userTable != null) { - File f = new File(userTable); - if (f.exists()) { - try { - return new FileInputStream(f); - } catch (IOException ignored) { - } - } - } - - // Standard location? - File f = new File(System.getProperty("java.home"), "lib" + File.separator + "content-types.properties"); - if (f.exists()) { - try { - return new FileInputStream(f); - } catch (IOException ignored) { - } - } - - return null; - } - - /** - * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your - * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins - * come from "$JAVA_HOME/lib/content-types.properties". - */ - private static void applyOverrides() { - // Get the appropriate InputStream to read overrides from, if any. - InputStream stream = getContentTypesPropertiesStream(); - if (stream == null) { - return; - } - - try { - try { - // Read the properties file... - Properties overrides = new Properties(); - overrides.load(stream); - // And translate its mapping to ours... - for (Map.Entry<Object, Object> entry : overrides.entrySet()) { - String extension = (String) entry.getKey(); - String mimeType = (String) entry.getValue(); - add(mimeType, extension); - } - } finally { - stream.close(); - } - } catch (IOException ignored) { - } - } - private MimeUtils() { } diff --git a/luni/src/main/java/libcore/net/NetworkSecurityPolicy.java b/luni/src/main/java/libcore/net/NetworkSecurityPolicy.java new file mode 100644 index 0000000..b1a41e8 --- /dev/null +++ b/luni/src/main/java/libcore/net/NetworkSecurityPolicy.java @@ -0,0 +1,66 @@ +/* + * 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.net; + +import libcore.net.url.FtpURLConnection; + +/** + * Network security policy for this process/application. + * + * <p>Network stacks/components are expected to honor this policy. Components which can use the + * Android framework API should be accessing this policy via the framework's + * {@code android.security.NetworkSecurityPolicy} instead of via this class. + * + * <p>The policy currently consists of a single flag: whether cleartext network traffic is + * permitted. See {@link #isCleartextTrafficPermitted()}. + */ +public class NetworkSecurityPolicy { + + private static volatile boolean cleartextTrafficPermitted = true; + + /** + * Returns whether cleartext network traffic (e.g. HTTP, FTP, XMPP, IMAP, SMTP -- without TLS or + * STARTTLS) is permitted for this process. + * + * <p>When cleartext network traffic is not permitted, the platform's components (e.g. HTTP + * stacks, {@code WebView}, {@code MediaPlayer}) will refuse this process's requests to use + * cleartext traffic. Third-party libraries are encouraged to do the same. + * + * <p>This flag is honored on a best effort basis because it's impossible to prevent all + * cleartext traffic from an application given the level of access provided to applications on + * Android. For example, there's no expectation that {@link java.net.Socket} API will honor this + * flag. Luckily, most network traffic from apps is handled by higher-level network stacks which + * can be made to honor this flag. Platform-provided network stacks (e.g. HTTP and FTP) honor + * this flag from day one, and well-established third-party network stacks will eventually + * honor it. + * + * <p>See {@link FtpURLConnection} for an example of honoring this flag. + */ + public static boolean isCleartextTrafficPermitted() { + return cleartextTrafficPermitted; + } + + /** + * Sets whether cleartext network traffic (e.g. HTTP, FTP, XMPP, IMAP, SMTP -- without TLS or + * STARTTLS) is permitted for this process. + * + * @see #isCleartextTrafficPermitted() + */ + public static void setCleartextTrafficPermitted(boolean permitted) { + cleartextTrafficPermitted = permitted; + } +} diff --git a/luni/src/main/java/libcore/net/http/ResponseUtils.java b/luni/src/main/java/libcore/net/http/ResponseUtils.java new file mode 100644 index 0000000..c892b53 --- /dev/null +++ b/luni/src/main/java/libcore/net/http/ResponseUtils.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 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.net.http; + +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @hide + */ +public class ResponseUtils { + /** + * Returns the response charset of a HTTP response based on the {@code Content-Type} of + * the response (see RFC 7230). If the {@code Content-Type} header is missing or invalid, + * the response is assumed to be encoded as {@code UTF-8}. Note that a charset usually + * makes sense only for {@code "text/plain"} and other "text based" responses. + * + * @throws IllegalCharsetNameException if the response specified charset is illegal. + * @throws UnsupportedCharsetException if the response specified charset is unsupported. + */ + public static Charset responseCharset(String contentTypeHeader) + throws IllegalCharsetNameException, UnsupportedCharsetException { + Charset responseCharset = StandardCharsets.UTF_8; + if (contentTypeHeader != null) { + Map<String, String> contentTypeParams = parseContentTypeParameters(contentTypeHeader); + String charsetParameter = contentTypeParams.get("charset"); + if (charsetParameter != null) { + responseCharset = Charset.forName(charsetParameter); + } + } + + return responseCharset; + } + + /** + * Parse content-type parameters. The format of this header is roughly : + * {@code type/subtype; param1=value1; param2=value2 ...} where each of the + * parameters are optional. Parsing is lenient, malformed parameters are ignored. + * + * Parameter keys & values are trimmed of whitespace and keys are converted to + * lower case. + */ + private static Map<String, String> parseContentTypeParameters(String contentTypeHeader) { + Map<String, String> parameters = Collections.EMPTY_MAP; + + String[] fields = contentTypeHeader.split(";"); + if (fields.length > 1) { + parameters = new HashMap<>(); + // Ignore the first element in the array (the type/subtype). + for (int i = 1; i < fields.length; ++i) { + final String parameter = fields[i]; + if (!parameter.isEmpty()) { + final String[] components = parameter.split("="); + if (components.length != 2) { + continue; + } + + final String key = components[0].trim().toLowerCase(); + final String value = components[1].trim(); + if (key.isEmpty() || value.isEmpty()) { + continue; + } + + parameters.put(key, value); + } + } + } + + return parameters; + } +} diff --git a/luni/src/main/java/libcore/net/url/FtpURLConnection.java b/luni/src/main/java/libcore/net/url/FtpURLConnection.java index 7594c3a..021bfa2 100644 --- a/luni/src/main/java/libcore/net/url/FtpURLConnection.java +++ b/luni/src/main/java/libcore/net/url/FtpURLConnection.java @@ -17,6 +17,7 @@ package libcore.net.url; +import libcore.net.NetworkSecurityPolicy; import java.io.BufferedInputStream; import java.io.EOFException; import java.io.FileNotFoundException; @@ -103,9 +104,15 @@ public class FtpURLConnection extends URLConnection { * * @param url */ - protected FtpURLConnection(URL url) { + protected FtpURLConnection(URL url) throws IOException { super(url); hostName = url.getHost(); + if (!NetworkSecurityPolicy.isCleartextTrafficPermitted()) { + // Cleartext network traffic is not permitted -- refuse this connection. + throw new IOException("Cleartext traffic not permitted: " + + url.getProtocol() + "://" + hostName + + ((url.getPort() >= 0) ? (":" + url.getPort()) : "")); + } String parse = url.getUserInfo(); if (parse != null) { int split = parse.indexOf(':'); @@ -118,7 +125,7 @@ public class FtpURLConnection extends URLConnection { } uri = null; try { - uri = url.toURI(); + uri = url.toURILenient(); } catch (URISyntaxException e) { // do nothing. } @@ -130,7 +137,7 @@ public class FtpURLConnection extends URLConnection { * @param url * @param proxy */ - protected FtpURLConnection(URL url, Proxy proxy) { + protected FtpURLConnection(URL url, Proxy proxy) throws IOException { this(url); this.proxy = proxy; } diff --git a/luni/src/main/java/java/nio/charset/Charsets.java b/luni/src/main/java/libcore/util/CharsetUtils.java index 3dede7a..2e426c4 100644 --- a/luni/src/main/java/java/nio/charset/Charsets.java +++ b/luni/src/main/java/libcore/util/CharsetUtils.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package java.nio.charset; +package libcore.util; /** * Various special-case charset conversions (for performance). * * @hide internal use only */ -public final class Charsets { +public final class CharsetUtils { /** * Returns a new byte array containing the bytes corresponding to the given characters, * encoded in US-ASCII. Unrepresentable characters are replaced by (byte) '?'. @@ -75,6 +75,6 @@ public final class Charsets { */ public static native void isoLatin1BytesToChars(byte[] bytes, int offset, int length, char[] chars); - private Charsets() { + private CharsetUtils() { } } diff --git a/luni/src/main/java/libcore/util/CountingOutputStream.java b/luni/src/main/java/libcore/util/CountingOutputStream.java new file mode 100644 index 0000000..cc0e1f2 --- /dev/null +++ b/luni/src/main/java/libcore/util/CountingOutputStream.java @@ -0,0 +1,59 @@ +/* + * 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.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * An output stream that keeps count of the number of bytes written to it. + * + * Useful when we need to make decisions based on the size of the output, such + * as deciding what sort of metadata to writes to zip files. + */ +public class CountingOutputStream extends FilterOutputStream { + + private long count; + + /** + * Constructs a new {@code FilterOutputStream} with {@code out} as its + * target stream. + * + * @param out the target stream that this stream writes to. + */ + public CountingOutputStream(OutputStream out) { + super(out); + count = 0; + } + + @Override + public void write(byte[] buffer, int offset, int length) throws IOException { + out.write(buffer, offset, length); + count += length; + } + + @Override + public void write(int oneByte) throws IOException { + out.write(oneByte); + count++; + } + + public long getCount() { + return count; + } +} diff --git a/luni/src/main/java/libcore/util/HexEncoding.java b/luni/src/main/java/libcore/util/HexEncoding.java new file mode 100644 index 0000000..f883a73 --- /dev/null +++ b/luni/src/main/java/libcore/util/HexEncoding.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.util; + +/** + * Hexadecimal encoding where each byte is represented by two hexadecimal digits. + */ +public class HexEncoding { + + /** Hidden constructor to prevent instantiation. */ + private HexEncoding() {} + + private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); + + /** + * Encodes the provided data as a sequence of hexadecimal characters. + */ + public static char[] encode(byte[] data) { + return encode(data, 0, data.length); + } + + /** + * Encodes the provided data as a sequence of hexadecimal characters. + */ + public static char[] encode(byte[] data, int offset, int len) { + char[] result = new char[len * 2]; + for (int i = 0; i < len; i++) { + byte b = data[offset + i]; + int resultIndex = 2 * i; + result[resultIndex] = (HEX_DIGITS[(b >>> 4) & 0x0f]); + result[resultIndex + 1] = (HEX_DIGITS[b & 0x0f]); + } + + return result; + } + + /** + * Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar} + * is {@code true} odd-length inputs are allowed and the first character is interpreted + * as the lower bits of the first result byte. + * + * Throws an {@code IllegalArgumentException} if the input is malformed. + */ + public static byte[] decode(char[] encoded, boolean allowSingleChar) throws IllegalArgumentException { + int resultLengthBytes = (encoded.length + 1) / 2; + byte[] result = new byte[resultLengthBytes]; + + int resultOffset = 0; + int i = 0; + if (allowSingleChar) { + if ((encoded.length % 2) != 0) { + // Odd number of digits -- the first digit is the lower 4 bits of the first result byte. + result[resultOffset++] = (byte) toDigit(encoded, i); + i++; + } + } else { + if ((encoded.length % 2) != 0) { + throw new IllegalArgumentException("Invalid input length: " + encoded.length); + } + } + + for (int len = encoded.length; i < len; i += 2) { + result[resultOffset++] = (byte) ((toDigit(encoded, i) << 4) | toDigit(encoded, i + 1)); + } + + return result; + } + + private static int toDigit(char[] str, int offset) throws IllegalArgumentException { + // NOTE: that this isn't really a code point in the traditional sense, since we're + // just rejecting surrogate pairs outright. + int pseudoCodePoint = str[offset]; + + if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') { + return pseudoCodePoint - '0'; + } else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') { + return 10 + (pseudoCodePoint - 'a'); + } else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') { + return 10 + (pseudoCodePoint - 'A'); + } + + throw new IllegalArgumentException("Illegal char: " + str[offset] + + " at offset " + offset); + } +} diff --git a/luni/src/main/java/libcore/util/ZoneInfo.java b/luni/src/main/java/libcore/util/ZoneInfo.java index 4d58d93..329320d 100644 --- a/luni/src/main/java/libcore/util/ZoneInfo.java +++ b/luni/src/main/java/libcore/util/ZoneInfo.java @@ -54,7 +54,7 @@ public final class ZoneInfo extends TimeZone { private final boolean mUseDst; private final int mDstSavings; // Implements TimeZone.getDSTSavings. - private final int[] mTransitions; + private final long[] mTransitions; private final int[] mOffsets; private final byte[] mTypes; private final byte[] mIsDsts; @@ -76,8 +76,19 @@ public final class ZoneInfo extends TimeZone { it.skip(4); // Skip tzh_charcnt. - int[] transitions = new int[tzh_timecnt]; - it.readIntArray(transitions, 0, transitions.length); + // Transitions are signed 32 bit integers, but we store them as signed 64 bit + // integers since it's easier to compare them against 64 bit inputs (see getOffset + // and isDaylightTime) with much less risk of an overflow in our calculations. + // + // The alternative of checking the input against the first and last transition in + // the array is far more awkward and error prone. + int[] transitions32 = new int[tzh_timecnt]; + it.readIntArray(transitions32, 0, transitions32.length); + + long[] transitions64 = new long[tzh_timecnt]; + for (int i = 0; i < tzh_timecnt; ++i) { + transitions64[i] = transitions32[i]; + } byte[] type = new byte[tzh_timecnt]; it.readByteArray(type, 0, type.length); @@ -97,10 +108,10 @@ public final class ZoneInfo extends TimeZone { it.skip(1); } - return new ZoneInfo(id, transitions, type, gmtOffsets, isDsts); + return new ZoneInfo(id, transitions64, type, gmtOffsets, isDsts); } - private ZoneInfo(String name, int[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts) { + private ZoneInfo(String name, long[] transitions, byte[] types, int[] gmtOffsets, byte[] isDsts) { mTransitions = transitions; mTypes = types; mIsDsts = isDsts; @@ -163,7 +174,7 @@ public final class ZoneInfo extends TimeZone { // no future plans (and thus no future schedule info) will report "true" from // useDaylightTime at the start of 2009 but "false" at the end. This seems appropriate. boolean usesDst = false; - int currentUnixTimeSeconds = (int) (System.currentTimeMillis() / 1000); + long currentUnixTimeSeconds = System.currentTimeMillis() / 1000; int i = mTransitions.length - 1; while (i >= 0 && mTransitions[i] >= currentUnixTimeSeconds) { if (mIsDsts[mTypes[i]] > 0) { @@ -210,7 +221,7 @@ public final class ZoneInfo extends TimeZone { @Override public int getOffset(long when) { - int unix = (int) (when / 1000); + long unix = when / 1000; int transition = Arrays.binarySearch(mTransitions, unix); if (transition < 0) { transition = ~transition - 1; @@ -226,7 +237,7 @@ public final class ZoneInfo extends TimeZone { @Override public boolean inDaylightTime(Date time) { long when = time.getTime(); - int unix = (int) (when / 1000); + long unix = when / 1000; int transition = Arrays.binarySearch(mTransitions, unix); if (transition < 0) { transition = ~transition - 1; @@ -952,9 +963,9 @@ public final class ZoneInfo extends TimeZone { * * @throws CheckedArithmeticException if overflow or underflow occurs */ - private static int checkedAdd(int a, int b) throws CheckedArithmeticException { + private static int checkedAdd(long a, int b) throws CheckedArithmeticException { // Adapted from Guava IntMath.checkedAdd(); - long result = (long) a + b; + long result = a + b; if (result != (int) result) { throw new CheckedArithmeticException(); } diff --git a/luni/src/main/java/libcore/util/ZoneInfoDB.java b/luni/src/main/java/libcore/util/ZoneInfoDB.java index 906ec14..b8e1dad 100644 --- a/luni/src/main/java/libcore/util/ZoneInfoDB.java +++ b/luni/src/main/java/libcore/util/ZoneInfoDB.java @@ -41,7 +41,8 @@ import libcore.io.MemoryMappedFile; */ public final class ZoneInfoDB { private static final TzData DATA = - new TzData(System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata"); + new TzData(System.getenv("ANDROID_DATA") + "/misc/zoneinfo/current/tzdata", + System.getenv("ANDROID_ROOT") + "/usr/share/zoneinfo/tzdata"); public static class TzData { /** diff --git a/luni/src/main/java/org/apache/harmony/security/fortress/Services.java b/luni/src/main/java/org/apache/harmony/security/fortress/Services.java index 30f4839..c81bf6b 100644 --- a/luni/src/main/java/org/apache/harmony/security/fortress/Services.java +++ b/luni/src/main/java/org/apache/harmony/security/fortress/Services.java @@ -63,6 +63,24 @@ public class Services { private static final ArrayList<Provider> providers = new ArrayList<Provider>(20); /** + * Try to load and register a provider by name from the given class-loader. + */ + private static boolean initProvider(String providerClassName, ClassLoader classLoader) { + try { + Class<?> providerClass = Class.forName(providerClassName.trim(), true, classLoader); + Provider p = (Provider) providerClass.newInstance(); + providers.add(p); + providersNames.put(p.getName(), p); + initServiceInfo(p); + return true; + } catch (ClassNotFoundException ignored) { + } catch (IllegalAccessException ignored) { + } catch (InstantiationException ignored) { + } + return false; + } + + /** * Hash for quick provider access by name. */ private static final HashMap<String, Provider> providersNames @@ -70,18 +88,16 @@ public class Services { static { String providerClassName = null; int i = 1; - ClassLoader cl = ClassLoader.getSystemClassLoader(); + ClassLoader cl = Services.class.getClassLoader(); while ((providerClassName = Security.getProperty("security.provider." + i++)) != null) { - try { - Class<?> providerClass = Class.forName(providerClassName.trim(), true, cl); - Provider p = (Provider) providerClass.newInstance(); - providers.add(p); - providersNames.put(p.getName(), p); - initServiceInfo(p); - } catch (ClassNotFoundException ignored) { - } catch (IllegalAccessException ignored) { - } catch (InstantiationException ignored) { + if (!initProvider(providerClassName, cl)) { + // Not on the boot classpath. Try the system class-loader. + // Note: DO NOT USE A LOCAL FOR GETSYSTEMCLASSLOADER! This will break compile-time + // initialization. + if (!initProvider(providerClassName, ClassLoader.getSystemClassLoader())) { + // TODO: Logging? + } } } Engine.door.renumProviders(); diff --git a/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java b/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java index 917a3a8..020663e 100644 --- a/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java +++ b/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java @@ -198,8 +198,8 @@ public class JarUtils { } } - // RFC 3852 section 9.2: it authAttrs is present, it must have a - // message digest entry. + // RFC 2315 section 9.1: if authenticatedAttributes is present, it + // must have a message-digest attribute. if (existingDigest == null) { throw new SecurityException("Missing MessageDigest in Authenticated Attributes"); } diff --git a/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java b/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java index 2b09309..1c07915 100644 --- a/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java +++ b/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java @@ -160,7 +160,7 @@ public class WrappedX509Certificate extends X509Certificate { public void verify(PublicKey key, String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { - verify(key, sigProvider); + wrapped.verify(key, sigProvider); } @Override diff --git a/luni/src/main/native/IcuUtilities.cpp b/luni/src/main/native/IcuUtilities.cpp index 7ce2168..98648a5 100644 --- a/luni/src/main/native/IcuUtilities.cpp +++ b/luni/src/main/native/IcuUtilities.cpp @@ -22,13 +22,12 @@ #include "JniException.h" #include "ScopedLocalRef.h" #include "ScopedUtfChars.h" -#include "UniquePtr.h" #include "cutils/log.h" #include "unicode/strenum.h" #include "unicode/uloc.h" #include "unicode/ustring.h" -jobjectArray fromStringEnumeration(JNIEnv* env, UErrorCode& status, const char* provider, StringEnumeration* se) { +jobjectArray fromStringEnumeration(JNIEnv* env, UErrorCode& status, const char* provider, icu::StringEnumeration* se) { if (maybeThrowIcuException(env, provider, status)) { return NULL; } @@ -40,7 +39,7 @@ jobjectArray fromStringEnumeration(JNIEnv* env, UErrorCode& status, const char* jobjectArray result = env->NewObjectArray(count, JniConstants::stringClass, NULL); for (int32_t i = 0; i < count; ++i) { - const UnicodeString* string = se->snext(status); + const icu::UnicodeString* string = se->snext(status); if (maybeThrowIcuException(env, "StringEnumeration::snext", status)) { return NULL; } diff --git a/luni/src/main/native/IcuUtilities.h b/luni/src/main/native/IcuUtilities.h index 737379e..c64de30 100644 --- a/luni/src/main/native/IcuUtilities.h +++ b/luni/src/main/native/IcuUtilities.h @@ -17,14 +17,11 @@ #ifndef ICU_UTILITIES_H_included #define ICU_UTILITIES_H_included -#undef U_HAVE_STD_STRING -#define U_HAVE_STD_STRING 1 // For UnicodeString::toUTF8String(std::string&). - #include "jni.h" #include "ustrenum.h" // For UStringEnumeration. #include "unicode/utypes.h" // For UErrorCode. -extern jobjectArray fromStringEnumeration(JNIEnv* env, UErrorCode& status, const char* provider, StringEnumeration*); +extern jobjectArray fromStringEnumeration(JNIEnv* env, UErrorCode& status, const char* provider, icu::StringEnumeration*); bool maybeThrowIcuException(JNIEnv* env, const char* function, UErrorCode error); #endif // ICU_UTILITIES_H_included diff --git a/luni/src/main/native/Portability.h b/luni/src/main/native/Portability.h index 1520311..5900cb0 100644 --- a/luni/src/main/native/Portability.h +++ b/luni/src/main/native/Portability.h @@ -65,7 +65,7 @@ static inline int mincore(void* addr, size_t length, unsigned char* vec) { #include <sys/param.h> #include <sys/mount.h> -#else // defined(__APPLE__) +#else // Bionic or glibc. @@ -73,15 +73,33 @@ static inline int mincore(void* addr, size_t length, unsigned char* vec) { #include <sys/sendfile.h> #include <sys/statvfs.h> -#endif // defined(__APPLE__) +#endif -#if !defined(__BIONIC__) #include <netdb.h> -#include "../../bionic/libc/dns/include/resolv_netid.h" -inline int android_getaddrinfofornet(const char *hostname, const char *servname, - const struct addrinfo *hints, unsigned /*netid*/, unsigned /*mark*/, struct addrinfo **res) { +#if defined(__BIONIC__) +extern "C" int android_getaddrinfofornet(const char*, const char*, const struct addrinfo*, unsigned, unsigned, struct addrinfo**); +#else +static inline int android_getaddrinfofornet(const char* hostname, const char* servname, + const struct addrinfo* hints, unsigned /*netid*/, unsigned /*mark*/, struct addrinfo** res) { return getaddrinfo(hostname, servname, hints, res); } -#endif // !defined(__BIONIC__) +#endif + +#if defined(__GLIBC__) && !defined(__LP64__) + +#include <unistd.h> + +// 32 bit GLIBC hardcodes a "long int" as the return type for +// TEMP_FAILURE_RETRY so the return value here gets truncated for +// functions that return 64 bit types. +#undef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(exp) ({ \ + __typeof__(exp) _rc; \ + do { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; }) + +#endif // __GLIBC__ && !__LP64__ #endif // PORTABILITY_H_included diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp index 6a2c939..0f2d0ad 100644 --- a/luni/src/main/native/Register.cpp +++ b/luni/src/main/native/Register.cpp @@ -48,7 +48,6 @@ jint JNI_OnLoad(JavaVM* vm, void*) { REGISTER(register_java_lang_System); REGISTER(register_java_math_NativeBN); REGISTER(register_java_nio_ByteOrder); - REGISTER(register_java_nio_charset_Charsets); REGISTER(register_java_text_Bidi); REGISTER(register_java_util_jar_StrictJarFile); REGISTER(register_java_util_regex_Matcher); @@ -58,9 +57,7 @@ jint JNI_OnLoad(JavaVM* vm, void*) { REGISTER(register_java_util_zip_Deflater); REGISTER(register_java_util_zip_Inflater); REGISTER(register_libcore_icu_AlphabeticIndex); - REGISTER(register_libcore_icu_DateIntervalFormat); REGISTER(register_libcore_icu_ICU); - REGISTER(register_libcore_icu_NativeBreakIterator); REGISTER(register_libcore_icu_NativeCollation); REGISTER(register_libcore_icu_NativeConverter); REGISTER(register_libcore_icu_NativeDecimalFormat); @@ -72,6 +69,7 @@ jint JNI_OnLoad(JavaVM* vm, void*) { REGISTER(register_libcore_io_AsynchronousCloseMonitor); REGISTER(register_libcore_io_Memory); REGISTER(register_libcore_io_Posix); + REGISTER(register_libcore_util_CharsetUtils); REGISTER(register_org_apache_harmony_dalvik_NativeTestTarget); REGISTER(register_org_apache_harmony_xml_ExpatParser); REGISTER(register_sun_misc_Unsafe); diff --git a/luni/src/main/native/ZipUtilities.cpp b/luni/src/main/native/ZipUtilities.cpp index 745b3b1..b7d2209 100644 --- a/luni/src/main/native/ZipUtilities.cpp +++ b/luni/src/main/native/ZipUtilities.cpp @@ -15,9 +15,10 @@ * limitations under the License. */ +#include <memory> + #include "JniConstants.h" #include "JniException.h" -#include "UniquePtr.h" #include "ZipUtilities.h" void throwExceptionForZlibError(JNIEnv* env, const char* exceptionClassName, int error, @@ -31,7 +32,7 @@ void throwExceptionForZlibError(JNIEnv* env, const char* exceptionClassName, int } } -NativeZipStream::NativeZipStream() : input(NULL), inCap(0), mDict(NULL) { +NativeZipStream::NativeZipStream() : inCap(0), totalIn(0), totalOut(0) { // Let zlib use its default allocator. stream.opaque = Z_NULL; stream.zalloc = Z_NULL; @@ -43,7 +44,7 @@ NativeZipStream::~NativeZipStream() { void NativeZipStream::setDictionary(JNIEnv* env, jbyteArray javaDictionary, int off, int len, bool inflate) { - UniquePtr<jbyte[]> dictionaryBytes(new jbyte[len]); + std::unique_ptr<jbyte[]> dictionaryBytes(new jbyte[len]); if (dictionaryBytes.get() == NULL) { jniThrowOutOfMemoryError(env, NULL); return; diff --git a/luni/src/main/native/ZipUtilities.h b/luni/src/main/native/ZipUtilities.h index fe0f977..50111a5 100644 --- a/luni/src/main/native/ZipUtilities.h +++ b/luni/src/main/native/ZipUtilities.h @@ -18,15 +18,18 @@ #ifndef ZIP_UTILITIES_H_included #define ZIP_UTILITIES_H_included -#include "UniquePtr.h" +#include <cstdint> +#include <memory> #include "jni.h" #include "zlib.h" class NativeZipStream { public: - UniquePtr<jbyte[]> input; + std::unique_ptr<jbyte[]> input; int inCap; z_stream stream; + uint64_t totalIn; + uint64_t totalOut; NativeZipStream(); ~NativeZipStream(); @@ -34,7 +37,7 @@ public: void setInput(JNIEnv* env, jbyteArray buf, jint off, jint len); private: - UniquePtr<jbyte[]> mDict; + std::unique_ptr<jbyte[]> mDict; // Disallow copy and assignment. NativeZipStream(const NativeZipStream&); diff --git a/luni/src/main/native/android_system_OsConstants.cpp b/luni/src/main/native/android_system_OsConstants.cpp index 92212b9..a9031f4 100644 --- a/luni/src/main/native/android_system_OsConstants.cpp +++ b/luni/src/main/native/android_system_OsConstants.cpp @@ -38,6 +38,9 @@ #include <sys/wait.h> #include <unistd.h> +#include <net/if_arp.h> +#include <linux/if_ether.h> + // After the others because these are not necessarily self-contained in glibc. #ifndef __APPLE__ #include <linux/if_addr.h> @@ -58,6 +61,8 @@ static void initConstant(JNIEnv* env, jclass c, const char* fieldName, int value static void OsConstants_initConstants(JNIEnv* env, jclass c) { initConstant(env, c, "AF_INET", AF_INET); initConstant(env, c, "AF_INET6", AF_INET6); + initConstant(env, c, "AF_PACKET", AF_PACKET); + initConstant(env, c, "AF_NETLINK", AF_NETLINK); initConstant(env, c, "AF_UNIX", AF_UNIX); initConstant(env, c, "AF_UNSPEC", AF_UNSPEC); initConstant(env, c, "AI_ADDRCONFIG", AI_ADDRCONFIG); @@ -69,6 +74,8 @@ static void OsConstants_initConstants(JNIEnv* env, jclass c) { #endif initConstant(env, c, "AI_PASSIVE", AI_PASSIVE); initConstant(env, c, "AI_V4MAPPED", AI_V4MAPPED); + initConstant(env, c, "ARPHRD_ETHER", ARPHRD_ETHER); + initConstant(env, c, "ARPHRD_LOOPBACK", ARPHRD_LOOPBACK); #if defined(CAP_LAST_CAP) initConstant(env, c, "CAP_AUDIT_CONTROL", CAP_AUDIT_CONTROL); initConstant(env, c, "CAP_AUDIT_WRITE", CAP_AUDIT_WRITE); @@ -196,6 +203,9 @@ static void OsConstants_initConstants(JNIEnv* env, jclass c) { initConstant(env, c, "ESPIPE", ESPIPE); initConstant(env, c, "ESRCH", ESRCH); initConstant(env, c, "ESTALE", ESTALE); + initConstant(env, c, "ETH_P_ARP", ETH_P_ARP); + initConstant(env, c, "ETH_P_IP", ETH_P_IP); + initConstant(env, c, "ETH_P_IPV6", ETH_P_IPV6); initConstant(env, c, "ETIME", ETIME); initConstant(env, c, "ETIMEDOUT", ETIMEDOUT); initConstant(env, c, "ETXTBSY", ETXTBSY); @@ -355,6 +365,7 @@ static void OsConstants_initConstants(JNIEnv* env, jclass c) { initConstant(env, c, "MS_ASYNC", MS_ASYNC); initConstant(env, c, "MS_INVALIDATE", MS_INVALIDATE); initConstant(env, c, "MS_SYNC", MS_SYNC); + initConstant(env, c, "NETLINK_ROUTE", NETLINK_ROUTE); initConstant(env, c, "NI_DGRAM", NI_DGRAM); initConstant(env, c, "NI_NAMEREQD", NI_NAMEREQD); initConstant(env, c, "NI_NOFQDN", NI_NOFQDN); @@ -362,6 +373,7 @@ static void OsConstants_initConstants(JNIEnv* env, jclass c) { initConstant(env, c, "NI_NUMERICSERV", NI_NUMERICSERV); initConstant(env, c, "O_ACCMODE", O_ACCMODE); initConstant(env, c, "O_APPEND", O_APPEND); + initConstant(env, c, "O_CLOEXEC", O_CLOEXEC); initConstant(env, c, "O_CREAT", O_CREAT); initConstant(env, c, "O_EXCL", O_EXCL); initConstant(env, c, "O_NOCTTY", O_NOCTTY); @@ -406,6 +418,19 @@ static void OsConstants_initConstants(JNIEnv* env, jclass c) { initConstant(env, c, "RT_SCOPE_NOWHERE", RT_SCOPE_NOWHERE); initConstant(env, c, "RT_SCOPE_SITE", RT_SCOPE_SITE); initConstant(env, c, "RT_SCOPE_UNIVERSE", RT_SCOPE_UNIVERSE); + initConstant(env, c, "RTMGRP_IPV4_IFADDR", RTMGRP_IPV4_IFADDR); + initConstant(env, c, "RTMGRP_IPV4_MROUTE", RTMGRP_IPV4_MROUTE); + initConstant(env, c, "RTMGRP_IPV4_ROUTE", RTMGRP_IPV4_ROUTE); + initConstant(env, c, "RTMGRP_IPV4_RULE", RTMGRP_IPV4_RULE); + initConstant(env, c, "RTMGRP_IPV6_IFADDR", RTMGRP_IPV6_IFADDR); + initConstant(env, c, "RTMGRP_IPV6_IFINFO", RTMGRP_IPV6_IFINFO); + initConstant(env, c, "RTMGRP_IPV6_MROUTE", RTMGRP_IPV6_MROUTE); + initConstant(env, c, "RTMGRP_IPV6_PREFIX", RTMGRP_IPV6_PREFIX); + initConstant(env, c, "RTMGRP_IPV6_ROUTE", RTMGRP_IPV6_ROUTE); + initConstant(env, c, "RTMGRP_LINK", RTMGRP_LINK); + initConstant(env, c, "RTMGRP_NEIGH", RTMGRP_NEIGH); + initConstant(env, c, "RTMGRP_NOTIFY", RTMGRP_NOTIFY); + initConstant(env, c, "RTMGRP_TC", RTMGRP_TC); #endif initConstant(env, c, "SEEK_CUR", SEEK_CUR); initConstant(env, c, "SEEK_END", SEEK_END); @@ -490,6 +515,15 @@ static void OsConstants_initConstants(JNIEnv* env, jclass c) { initConstant(env, c, "STDERR_FILENO", STDERR_FILENO); initConstant(env, c, "STDIN_FILENO", STDIN_FILENO); initConstant(env, c, "STDOUT_FILENO", STDOUT_FILENO); + initConstant(env, c, "ST_MANDLOCK", ST_MANDLOCK); + initConstant(env, c, "ST_NOATIME", ST_NOATIME); + initConstant(env, c, "ST_NODEV", ST_NODEV); + initConstant(env, c, "ST_NODIRATIME", ST_NODIRATIME); + initConstant(env, c, "ST_NOEXEC", ST_NOEXEC); + initConstant(env, c, "ST_NOSUID", ST_NOSUID); + initConstant(env, c, "ST_RDONLY", ST_RDONLY); + initConstant(env, c, "ST_RELATIME", ST_RELATIME); + initConstant(env, c, "ST_SYNCHRONOUS", ST_SYNCHRONOUS); initConstant(env, c, "S_IFBLK", S_IFBLK); initConstant(env, c, "S_IFCHR", S_IFCHR); initConstant(env, c, "S_IFDIR", S_IFDIR); diff --git a/luni/src/main/native/java_lang_StrictMath.cpp b/luni/src/main/native/java_lang_StrictMath.cpp index cfe375e..e8c6dfb 100644 --- a/luni/src/main/native/java_lang_StrictMath.cpp +++ b/luni/src/main/native/java_lang_StrictMath.cpp @@ -34,26 +34,6 @@ static jdouble StrictMath_tan(JNIEnv*, jclass, jdouble a) { return ieee_tan(a); } -static jdouble StrictMath_asin(JNIEnv*, jclass, jdouble a) { - return ieee_asin(a); -} - -static jdouble StrictMath_acos(JNIEnv*, jclass, jdouble a) { - return ieee_acos(a); -} - -static jdouble StrictMath_atan(JNIEnv*, jclass, jdouble a) { - return ieee_atan(a); -} - -static jdouble StrictMath_exp(JNIEnv*, jclass, jdouble a) { - return ieee_exp(a); -} - -static jdouble StrictMath_log(JNIEnv*, jclass, jdouble a) { - return ieee_log(a); -} - static jdouble StrictMath_sqrt(JNIEnv*, jclass, jdouble a) { return ieee_sqrt(a); } @@ -74,75 +54,30 @@ static jdouble StrictMath_rint(JNIEnv*, jclass, jdouble a) { return ieee_rint(a); } -static jdouble StrictMath_atan2(JNIEnv*, jclass, jdouble a, jdouble b) { - return ieee_atan2(a, b); -} - static jdouble StrictMath_pow(JNIEnv*, jclass, jdouble a, jdouble b) { return ieee_pow(a,b); } -static jdouble StrictMath_sinh(JNIEnv*, jclass, jdouble a) { - return ieee_sinh(a); -} - -static jdouble StrictMath_tanh(JNIEnv*, jclass, jdouble a) { - return ieee_tanh(a); -} - -static jdouble StrictMath_cosh(JNIEnv*, jclass, jdouble a) { - return ieee_cosh(a); -} - -static jdouble StrictMath_log10(JNIEnv*, jclass, jdouble a) { - return ieee_log10(a); -} - -static jdouble StrictMath_cbrt(JNIEnv*, jclass, jdouble a) { - return ieee_cbrt(a); -} - -static jdouble StrictMath_expm1(JNIEnv*, jclass, jdouble a) { - return ieee_expm1(a); -} - static jdouble StrictMath_hypot(JNIEnv*, jclass, jdouble a, jdouble b) { return ieee_hypot(a, b); } -static jdouble StrictMath_log1p(JNIEnv*, jclass, jdouble a) { - return ieee_log1p(a); -} - static jdouble StrictMath_nextafter(JNIEnv*, jclass, jdouble a, jdouble b) { return ieee_nextafter(a, b); } static JNINativeMethod gMethods[] = { NATIVE_METHOD(StrictMath, IEEEremainder, "!(DD)D"), - NATIVE_METHOD(StrictMath, acos, "!(D)D"), - NATIVE_METHOD(StrictMath, asin, "!(D)D"), - NATIVE_METHOD(StrictMath, atan, "!(D)D"), - NATIVE_METHOD(StrictMath, atan2, "!(DD)D"), - NATIVE_METHOD(StrictMath, cbrt, "!(D)D"), NATIVE_METHOD(StrictMath, ceil, "!(D)D"), NATIVE_METHOD(StrictMath, cos, "!(D)D"), - NATIVE_METHOD(StrictMath, cosh, "!(D)D"), - NATIVE_METHOD(StrictMath, exp, "!(D)D"), - NATIVE_METHOD(StrictMath, expm1, "!(D)D"), NATIVE_METHOD(StrictMath, floor, "!(D)D"), NATIVE_METHOD(StrictMath, hypot, "!(DD)D"), - NATIVE_METHOD(StrictMath, log, "!(D)D"), - NATIVE_METHOD(StrictMath, log10, "!(D)D"), - NATIVE_METHOD(StrictMath, log1p, "!(D)D"), NATIVE_METHOD(StrictMath, nextafter, "!(DD)D"), NATIVE_METHOD(StrictMath, pow, "!(DD)D"), NATIVE_METHOD(StrictMath, rint, "!(D)D"), NATIVE_METHOD(StrictMath, sin, "!(D)D"), - NATIVE_METHOD(StrictMath, sinh, "!(D)D"), NATIVE_METHOD(StrictMath, sqrt, "!(D)D"), NATIVE_METHOD(StrictMath, tan, "!(D)D"), - NATIVE_METHOD(StrictMath, tanh, "!(D)D"), }; void register_java_lang_StrictMath(JNIEnv* env) { jniRegisterNativeMethods(env, "java/lang/StrictMath", gMethods, NELEM(gMethods)); diff --git a/luni/src/main/native/java_lang_StringToReal.cpp b/luni/src/main/native/java_lang_StringToReal.cpp index 108f939..d1902af 100644 --- a/luni/src/main/native/java_lang_StringToReal.cpp +++ b/luni/src/main/native/java_lang_StringToReal.cpp @@ -25,9 +25,6 @@ #include "cbigint.h" /* ************************* Defines ************************* */ -#if defined(__linux__) || defined(__APPLE__) -#define USE_LL -#endif #define LOW_I32_FROM_VAR(u64) LOW_I32_FROM_LONG64(u64) #define LOW_I32_FROM_PTR(u64ptr) LOW_I32_FROM_LONG64_PTR(u64ptr) @@ -38,69 +35,13 @@ #define DEFAULT_DOUBLE_WIDTH MAX_DOUBLE_ACCURACY_WIDTH -#if defined(USE_LL) #define DOUBLE_INFINITE_LONGBITS (0x7FF0000000000000LL) -#else -#if defined(USE_L) -#define DOUBLE_INFINITE_LONGBITS (0x7FF0000000000000L) -#else -#define DOUBLE_INFINITE_LONGBITS (0x7FF0000000000000) -#endif /* USE_L */ -#endif /* USE_LL */ #define DOUBLE_MINIMUM_LONGBITS (0x1) -#if defined(USE_LL) #define DOUBLE_MANTISSA_MASK (0x000FFFFFFFFFFFFFLL) #define DOUBLE_EXPONENT_MASK (0x7FF0000000000000LL) #define DOUBLE_NORMAL_MASK (0x0010000000000000LL) -#else -#if defined(USE_L) -#define DOUBLE_MANTISSA_MASK (0x000FFFFFFFFFFFFFL) -#define DOUBLE_EXPONENT_MASK (0x7FF0000000000000L) -#define DOUBLE_NORMAL_MASK (0x0010000000000000L) -#else -#define DOUBLE_MANTISSA_MASK (0x000FFFFFFFFFFFFF) -#define DOUBLE_EXPONENT_MASK (0x7FF0000000000000) -#define DOUBLE_NORMAL_MASK (0x0010000000000000) -#endif /* USE_L */ -#endif /* USE_LL */ - -/* Keep a count of the number of times we decrement and increment to - * approximate the double, and attempt to detect the case where we - * could potentially toggle back and forth between decrementing and - * incrementing. It is possible for us to be stuck in the loop when - * incrementing by one or decrementing by one may exceed or stay below - * the value that we are looking for. In this case, just break out of - * the loop if we toggle between incrementing and decrementing for more - * than twice. - */ -#define INCREMENT_DOUBLE(_x, _decCount, _incCount) \ - { \ - ++DOUBLE_TO_LONGBITS(_x); \ - _incCount++; \ - if( (_incCount > 2) && (_decCount > 2) ) { \ - if( _decCount > _incCount ) { \ - DOUBLE_TO_LONGBITS(_x) += _decCount - _incCount; \ - } else if( _incCount > _decCount ) { \ - DOUBLE_TO_LONGBITS(_x) -= _incCount - _decCount; \ - } \ - break; \ - } \ - } -#define DECREMENT_DOUBLE(_x, _decCount, _incCount) \ - { \ - --DOUBLE_TO_LONGBITS(_x); \ - _decCount++; \ - if( (_incCount > 2) && (_decCount > 2) ) { \ - if( _decCount > _incCount ) { \ - DOUBLE_TO_LONGBITS(_x) += _decCount - _incCount; \ - } else if( _incCount > _decCount ) { \ - DOUBLE_TO_LONGBITS(_x) -= _incCount - _decCount; \ - } \ - break; \ - } \ - } #define allocateU64(x, n) if (!((x) = reinterpret_cast<uint64_t*>(malloc((n) * sizeof(uint64_t))))) goto OutOfMemory; @@ -248,7 +189,6 @@ static jdouble createDouble(JNIEnv* env, const char* s, jint e) { } return result; - } static jdouble createDouble1(JNIEnv* env, uint64_t* f, int32_t length, jint e) { @@ -310,7 +250,6 @@ static jdouble createDouble1(JNIEnv* env, uint64_t* f, int32_t length, jint e) { first and let it fall to zero if need be. */ if (result == 0.0) - DOUBLE_TO_LONGBITS (result) = DOUBLE_MINIMUM_LONGBITS; return doubleAlgorithm (env, f, length, e, result); @@ -323,15 +262,6 @@ static jdouble createDouble1(JNIEnv* env, uint64_t* f, int32_t length, jint e) { * Clinger, Proceedings of the ACM SIGPLAN '90 Conference on * Programming Language Design and Implementation, June 20-22, * 1990, pp. 92-101. - * - * There is a possibility that the function will end up in an endless - * loop if the given approximating floating-point number (a very small - * floating-point whose value is very close to zero) straddles between - * two approximating integer values. We modified the algorithm slightly - * to detect the case where it oscillates back and forth between - * incrementing and decrementing the floating-point approximation. It - * is currently set such that if the oscillation occurs more than twice - * then return the original approximation. */ static jdouble doubleAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, jdouble z) { uint64_t m; @@ -340,11 +270,10 @@ static jdouble doubleAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, uint64_t* y; uint64_t* D; uint64_t* D2; - int32_t xLength, yLength, DLength, D2Length, decApproxCount, incApproxCount; + int32_t xLength, yLength, DLength, D2Length; x = y = D = D2 = 0; xLength = yLength = DLength = D2Length = 0; - decApproxCount = incApproxCount = 0; do { @@ -443,12 +372,13 @@ static jdouble doubleAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, comparison2 = compareHighPrecision (D2, D2Length, y, yLength); if (comparison2 < 0) { - if (comparison < 0 && m == DOUBLE_NORMAL_MASK) + if (comparison < 0 && m == DOUBLE_NORMAL_MASK + && DOUBLE_TO_LONGBITS(z) != DOUBLE_NORMAL_MASK) { simpleShiftLeftHighPrecision (D2, D2Length, 1); if (compareHighPrecision (D2, D2Length, y, yLength) > 0) { - DECREMENT_DOUBLE (z, decApproxCount, incApproxCount); + --DOUBLE_TO_LONGBITS (z); } else { @@ -466,7 +396,7 @@ static jdouble doubleAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, { if (comparison < 0 && m == DOUBLE_NORMAL_MASK) { - DECREMENT_DOUBLE (z, decApproxCount, incApproxCount); + --DOUBLE_TO_LONGBITS (z); } else { @@ -475,24 +405,24 @@ static jdouble doubleAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, } else if (comparison < 0) { - DECREMENT_DOUBLE (z, decApproxCount, incApproxCount); + --DOUBLE_TO_LONGBITS (z); break; } else { - INCREMENT_DOUBLE (z, decApproxCount, incApproxCount); + ++DOUBLE_TO_LONGBITS (z); break; } } else if (comparison < 0) { - DECREMENT_DOUBLE (z, decApproxCount, incApproxCount); + --DOUBLE_TO_LONGBITS (z); } else { if (DOUBLE_TO_LONGBITS (z) == DOUBLE_INFINITE_LONGBITS) break; - INCREMENT_DOUBLE (z, decApproxCount, incApproxCount); + ++DOUBLE_TO_LONGBITS (z); } } while (1); @@ -547,42 +477,6 @@ static const uint32_t float_tens[] = { #define FLOAT_EXPONENT_MASK (0x7F800000) #define FLOAT_NORMAL_MASK (0x00800000) -/* Keep a count of the number of times we decrement and increment to - * approximate the double, and attempt to detect the case where we - * could potentially toggle back and forth between decrementing and - * incrementing. It is possible for us to be stuck in the loop when - * incrementing by one or decrementing by one may exceed or stay below - * the value that we are looking for. In this case, just break out of - * the loop if we toggle between incrementing and decrementing for more - * than twice. - */ -#define INCREMENT_FLOAT(_x, _decCount, _incCount) \ - { \ - ++FLOAT_TO_INTBITS(_x); \ - _incCount++; \ - if( (_incCount > 2) && (_decCount > 2) ) { \ - if( _decCount > _incCount ) { \ - FLOAT_TO_INTBITS(_x) += _decCount - _incCount; \ - } else if( _incCount > _decCount ) { \ - FLOAT_TO_INTBITS(_x) -= _incCount - _decCount; \ - } \ - break; \ - } \ - } -#define DECREMENT_FLOAT(_x, _decCount, _incCount) \ - { \ - --FLOAT_TO_INTBITS(_x); \ - _decCount++; \ - if( (_incCount > 2) && (_decCount > 2) ) { \ - if( _decCount > _incCount ) { \ - FLOAT_TO_INTBITS(_x) += _decCount - _incCount; \ - } else if( _incCount > _decCount ) { \ - FLOAT_TO_INTBITS(_x) -= _incCount - _decCount; \ - } \ - break; \ - } \ - } - static jfloat createFloat(JNIEnv* env, const char* s, jint e) { /* assumes s is a null terminated string with at least one * character in it */ @@ -682,7 +576,6 @@ static jfloat createFloat(JNIEnv* env, const char* s, jint e) { } return result; - } static jfloat createFloat1 (JNIEnv* env, uint64_t* f, int32_t length, jint e) { @@ -796,15 +689,6 @@ static jfloat createFloat1 (JNIEnv* env, uint64_t* f, int32_t length, jint e) { * Clinger, Proceedings of the ACM SIGPLAN '90 Conference on * Programming Language Design and Implementation, June 20-22, * 1990, pp. 92-101. - * - * There is a possibility that the function will end up in an endless - * loop if the given approximating floating-point number (a very small - * floating-point whose value is very close to zero) straddles between - * two approximating integer values. We modified the algorithm slightly - * to detect the case where it oscillates back and forth between - * incrementing and decrementing the floating-point approximation. It - * is currently set such that if the oscillation occurs more than twice - * then return the original approximation. */ static jfloat floatAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, jfloat z) { uint64_t m; @@ -814,11 +698,9 @@ static jfloat floatAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, j uint64_t* D; uint64_t* D2; int32_t xLength, yLength, DLength, D2Length; - int32_t decApproxCount, incApproxCount; x = y = D = D2 = 0; xLength = yLength = DLength = D2Length = 0; - decApproxCount = incApproxCount = 0; do { @@ -917,12 +799,13 @@ static jfloat floatAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, j comparison2 = compareHighPrecision (D2, D2Length, y, yLength); if (comparison2 < 0) { - if (comparison < 0 && m == FLOAT_NORMAL_MASK) + if (comparison < 0 && m == FLOAT_NORMAL_MASK + && FLOAT_TO_INTBITS(z) != FLOAT_NORMAL_MASK) { simpleShiftLeftHighPrecision (D2, D2Length, 1); if (compareHighPrecision (D2, D2Length, y, yLength) > 0) { - DECREMENT_FLOAT (z, decApproxCount, incApproxCount); + --FLOAT_TO_INTBITS (z); } else { @@ -940,7 +823,7 @@ static jfloat floatAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, j { if (comparison < 0 && m == FLOAT_NORMAL_MASK) { - DECREMENT_FLOAT (z, decApproxCount, incApproxCount); + --FLOAT_TO_INTBITS (z); } else { @@ -949,24 +832,24 @@ static jfloat floatAlgorithm(JNIEnv* env, uint64_t* f, int32_t length, jint e, j } else if (comparison < 0) { - DECREMENT_FLOAT (z, decApproxCount, incApproxCount); + --FLOAT_TO_INTBITS (z); break; } else { - INCREMENT_FLOAT (z, decApproxCount, incApproxCount); + ++FLOAT_TO_INTBITS (z); break; } } else if (comparison < 0) { - DECREMENT_FLOAT (z, decApproxCount, incApproxCount); + --FLOAT_TO_INTBITS (z); } else { if (FLOAT_TO_INTBITS (z) == FLOAT_EXPONENT_MASK) break; - INCREMENT_FLOAT (z, decApproxCount, incApproxCount); + ++FLOAT_TO_INTBITS (z); } } while (1); diff --git a/luni/src/main/native/java_lang_System.cpp b/luni/src/main/native/java_lang_System.cpp index 944c0c3..306adab 100644 --- a/luni/src/main/native/java_lang_System.cpp +++ b/luni/src/main/native/java_lang_System.cpp @@ -84,7 +84,11 @@ static jobjectArray System_specialProperties(JNIEnv* env, jclass) { properties.push_back(std::string("user.dir=") + getcwd(path, sizeof(path))); properties.push_back("android.zlib.version=" ZLIB_VERSION); +#if defined(OPENSSL_IS_BORINGSSL) + properties.push_back("android.openssl.version=BoringSSL"); +#else properties.push_back("android.openssl.version=" OPENSSL_VERSION_TEXT); +#endif const char* library_path = getenv("LD_LIBRARY_PATH"); #if defined(HAVE_ANDROID_OS) @@ -109,33 +113,20 @@ static jlong System_currentTimeMillis(JNIEnv*, jclass) { } static jlong System_nanoTime(JNIEnv*, jclass) { -#if defined(HAVE_POSIX_CLOCKS) +#if defined(__linux__) timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return now.tv_sec * 1000000000LL + now.tv_nsec; -#else +#else // __APPLE__ timeval now; gettimeofday(&now, NULL); return static_cast<jlong>(now.tv_sec) * 1000000000LL + now.tv_usec * 1000LL; #endif } -static jstring System_mapLibraryName(JNIEnv* env, jclass, jstring javaName) { - ScopedUtfChars name(env, javaName); - if (name.c_str() == NULL) { - return NULL; - } - char* mappedName = NULL; - asprintf(&mappedName, OS_SHARED_LIB_FORMAT_STR, name.c_str()); - jstring result = env->NewStringUTF(mappedName); - free(mappedName); - return result; -} - static JNINativeMethod gMethods[] = { NATIVE_METHOD(System, currentTimeMillis, "!()J"), NATIVE_METHOD(System, log, "(CLjava/lang/String;Ljava/lang/Throwable;)V"), - NATIVE_METHOD(System, mapLibraryName, "(Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(System, nanoTime, "!()J"), NATIVE_METHOD(System, setFieldImpl, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"), NATIVE_METHOD(System, specialProperties, "()[Ljava/lang/String;"), diff --git a/luni/src/main/native/java_math_NativeBN.cpp b/luni/src/main/native/java_math_NativeBN.cpp index be87ea6..7e77e23 100644 --- a/luni/src/main/native/java_math_NativeBN.cpp +++ b/luni/src/main/native/java_math_NativeBN.cpp @@ -21,19 +21,31 @@ #include "JniException.h" #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" -#include "UniquePtr.h" #include "jni.h" #include <openssl/bn.h> #include <openssl/crypto.h> #include <openssl/err.h> #include <stdio.h> +#include <memory> + +#if defined(OPENSSL_IS_BORINGSSL) +/* BoringSSL no longer exports |bn_check_top|. */ +static void bn_check_top(const BIGNUM* bn) { + /* This asserts that |bn->top| (which contains the number of elements of + * |bn->d| that are valid) is minimal. In other words, that there aren't + * superfluous zeros. */ + if (bn != NULL && bn->top != 0 && bn->d[bn->top-1] == 0) { + abort(); + } +} +#endif struct BN_CTX_Deleter { void operator()(BN_CTX* p) const { BN_CTX_free(p); } }; -typedef UniquePtr<BN_CTX, BN_CTX_Deleter> Unique_BN_CTX; +typedef std::unique_ptr<BN_CTX, BN_CTX_Deleter> Unique_BN_CTX; static BIGNUM* toBigNum(jlong address) { return reinterpret_cast<BIGNUM*>(static_cast<uintptr_t>(address)); @@ -109,28 +121,32 @@ static void NativeBN_BN_copy(JNIEnv* env, jclass, jlong to, jlong from) { } static void NativeBN_putULongInt(JNIEnv* env, jclass, jlong a0, jlong java_dw, jboolean neg) { - if (!oneValidHandle(env, a0)) return; + if (!oneValidHandle(env, a0)) return; - uint64_t dw = java_dw; + uint64_t dw = java_dw; + BIGNUM* a = toBigNum(a0); + int ok; - // cf. litEndInts2bn: - BIGNUM* a = toBigNum(a0); - bn_check_top(a); - if (bn_wexpand(a, 8/BN_BYTES) != NULL) { -#ifdef __LP64__ + static_assert(sizeof(dw) == sizeof(BN_ULONG) || + sizeof(dw) == 2*sizeof(BN_ULONG), "Unknown BN configuration"); + + if (sizeof(dw) == sizeof(BN_ULONG)) { + ok = BN_set_word(a, dw); + } else if (sizeof(dw) == 2 * sizeof(BN_ULONG)) { + ok = (bn_wexpand(a, 2) != NULL); + if (ok) { a->d[0] = dw; -#else - unsigned int hi = dw >> 32; // This shifts without sign extension. - int lo = (int)dw; // This truncates implicitly. - a->d[0] = lo; - a->d[1] = hi; -#endif - a->top = 8 / BN_BYTES; - a->neg = neg; + a->d[1] = dw >> 32; + a->top = 2; bn_correct_top(a); - } else { - throwExceptionIfNecessary(env); } + } + + BN_set_negative(a, neg); + + if (!ok) { + throwExceptionIfNecessary(env); + } } static void NativeBN_putLongInt(JNIEnv* env, jclass cls, jlong a, jlong dw) { @@ -240,25 +256,25 @@ static void negBigEndianBytes2bn(JNIEnv*, jclass, const unsigned char* bytes, in bn_check_top(ret); // FIXME: assert bytesLen > 0 - int wLen = (bytesLen + BN_BYTES - 1) / BN_BYTES; + int wLen = (bytesLen + sizeof(BN_ULONG) - 1) / sizeof(BN_ULONG); int firstNonzeroDigit = -2; if (bn_wexpand(ret, wLen) != NULL) { BN_ULONG* d = ret->d; BN_ULONG di; ret->top = wLen; - int highBytes = bytesLen % BN_BYTES; + int highBytes = bytesLen % sizeof(BN_ULONG); int k = bytesLen; // Put bytes to the int array starting from the end of the byte array int i = 0; while (k > highBytes) { - k -= BN_BYTES; + k -= sizeof(BN_ULONG); di = BYTES2ULONG(bytes, k); if (di != 0) { d[i] = -di; firstNonzeroDigit = i; i++; while (k > highBytes) { - k -= BN_BYTES; + k -= sizeof(BN_ULONG); d[i] = ~BYTES2ULONG(bytes, k); i++; } @@ -394,7 +410,7 @@ static jintArray NativeBN_bn2litEndInts(JNIEnv* env, jclass, jlong a0) { if (wLen == 0) { return NULL; } - jintArray result = env->NewIntArray(wLen * BN_BYTES/sizeof(unsigned int)); + jintArray result = env->NewIntArray(wLen * sizeof(BN_ULONG)/sizeof(unsigned int)); if (result == NULL) { return NULL; } @@ -445,7 +461,7 @@ static int NativeBN_bitLength(JNIEnv* env, jclass, jlong a0) { do { i--; } while (!((i < 0) || (d[i] != 0))); if (i < 0) msd--; // Only if all lower significant digits are 0 we decrement the most significant one. } - return (wLen - 1) * BN_BYTES * 8 + BN_num_bits_word(msd); + return (wLen - 1) * sizeof(BN_ULONG) * 8 + BN_num_bits_word(msd); } static jboolean NativeBN_BN_is_bit_set(JNIEnv* env, jclass, jlong a, int n) { diff --git a/luni/src/main/native/java_text_Bidi.cpp b/luni/src/main/native/java_text_Bidi.cpp index d9ef35d..6a3e751 100644 --- a/luni/src/main/native/java_text_Bidi.cpp +++ b/luni/src/main/native/java_text_Bidi.cpp @@ -22,14 +22,14 @@ #include "JniConstants.h" #include "JniException.h" #include "ScopedPrimitiveArray.h" -#include "UniquePtr.h" #include "unicode/ubidi.h" #include <stdlib.h> #include <string.h> +#include <memory> struct BiDiData { - BiDiData(UBiDi* biDi) : mBiDi(biDi), mEmbeddingLevels(NULL) { + BiDiData(UBiDi* biDi) : mBiDi(biDi) { } ~BiDiData() { @@ -50,7 +50,7 @@ struct BiDiData { private: UBiDi* mBiDi; - UniquePtr<jbyte[]> mEmbeddingLevels; + std::unique_ptr<jbyte[]> mEmbeddingLevels; // Disallow copy and assignment. BiDiData(const BiDiData&); @@ -98,7 +98,7 @@ static jlong Bidi_ubidi_setLine(JNIEnv* env, jclass, jlong ptr, jint start, jint if (maybeThrowIcuException(env, "ubidi_openSized", status)) { return 0; } - UniquePtr<BiDiData> lineData(new BiDiData(sized)); + std::unique_ptr<BiDiData> lineData(new BiDiData(sized)); ubidi_setLine(uBiDi(ptr), start, limit, lineData->uBiDi(), &status); maybeThrowIcuException(env, "ubidi_setLine", status); return reinterpret_cast<uintptr_t>(lineData.release()); @@ -168,7 +168,7 @@ static jintArray Bidi_ubidi_reorderVisual(JNIEnv* env, jclass, jbyteArray javaLe const UBiDiLevel* levels = reinterpret_cast<const UBiDiLevel*>(levelBytes.get()); - UniquePtr<int[]> indexMap(new int[length]); + std::unique_ptr<int[]> indexMap(new int[length]); ubidi_reorderVisual(levels, length, &indexMap[0]); jintArray result = env->NewIntArray(length); diff --git a/luni/src/main/native/java_util_jar_StrictJarFile.cpp b/luni/src/main/native/java_util_jar_StrictJarFile.cpp index efcc74c..82547bd 100644 --- a/luni/src/main/native/java_util_jar_StrictJarFile.cpp +++ b/luni/src/main/native/java_util_jar_StrictJarFile.cpp @@ -17,49 +17,26 @@ #define LOG_TAG "StrictJarFile" +#include <memory> #include <string> #include "JNIHelp.h" #include "JniConstants.h" #include "ScopedLocalRef.h" #include "ScopedUtfChars.h" -#include "UniquePtr.h" #include "jni.h" #include "ziparchive/zip_archive.h" #include "cutils/log.h" +// The method ID for ZipEntry.<init>(String,String,JJJIII[BJJ) +static jmethodID zipEntryCtor; + static void throwIoException(JNIEnv* env, const int32_t errorCode) { jniThrowException(env, "java/io/IOException", ErrorCodeString(errorCode)); } -// Constructs a string out of |name| with the default charset (UTF-8 on android). -// We prefer this to JNI's NewStringUTF because the string constructor will -// replace unmappable and malformed bytes instead of throwing. See b/18584205 -// -// Returns |NULL| iff. we couldn't allocate the string object or its constructor -// arguments. -// -// TODO: switch back to NewStringUTF after libziparchive is modified to reject -// files whose names aren't valid UTF-8. -static jobject constructString(JNIEnv* env, const char* name, const uint16_t nameLength) { - jbyteArray javaNameBytes = env->NewByteArray(nameLength); - if (javaNameBytes == NULL) { - return NULL; - } - env->SetByteArrayRegion(javaNameBytes, 0, nameLength, reinterpret_cast<const jbyte*>(name)); - - ScopedLocalRef<jclass> stringClass(env, env->FindClass("java/lang/String")); - const jmethodID stringCtor = env->GetMethodID(stringClass.get(), "<init>", "([B)V"); - return env->NewObject(stringClass.get(), stringCtor, javaNameBytes); -} - -static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, const jobject entryName, - const uint16_t nameLength) { - ScopedLocalRef<jclass> zipEntryClass(env, env->FindClass("java/util/zip/ZipEntry")); - const jmethodID zipEntryCtor = env->GetMethodID(zipEntryClass.get(), "<init>", - "(Ljava/lang/String;Ljava/lang/String;JJJIII[BIJJ)V"); - - return env->NewObject(zipEntryClass.get(), +static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, jstring entryName) { + return env->NewObject(JniConstants::zipEntryClass, zipEntryCtor, entryName, NULL, // comment @@ -70,16 +47,10 @@ static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, const jobject ent static_cast<jint>(0), // time static_cast<jint>(0), // modData NULL, // byte[] extra - static_cast<jint>(nameLength), static_cast<jlong>(-1), // local header offset static_cast<jlong>(entry.offset)); } -static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, const char* name, - const uint16_t nameLength) { - return newZipEntry(env, entry, constructString(env, name, nameLength), nameLength); -} - static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring fileName) { ScopedUtfChars fileChars(env, fileName); if (fileChars.c_str() == NULL) { @@ -98,25 +69,20 @@ static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring fileN class IterationHandle { public: - IterationHandle(const char* prefix) : - cookie_(NULL), prefix_(strdup(prefix)) { + IterationHandle() : + cookie_(NULL) { } void** CookieAddress() { return &cookie_; } - const char* Prefix() const { - return prefix_; - } - ~IterationHandle() { - free(prefix_); + EndIteration(cookie_); } private: void* cookie_; - char* prefix_; }; @@ -127,14 +93,15 @@ static jlong StrictJarFile_nativeStartIteration(JNIEnv* env, jobject, jlong nati return static_cast<jlong>(-1); } - IterationHandle* handle = new IterationHandle(prefixChars.c_str()); + IterationHandle* handle = new IterationHandle(); int32_t error = 0; if (prefixChars.size() == 0) { error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle), handle->CookieAddress(), NULL); } else { + ZipEntryName entry_name(prefixChars.c_str()); error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle), - handle->CookieAddress(), handle->Prefix()); + handle->CookieAddress(), &entry_name); } if (error) { @@ -156,11 +123,12 @@ static jobject StrictJarFile_nativeNextEntry(JNIEnv* env, jobject, jlong iterati return NULL; } - UniquePtr<char[]> entryNameCString(new char[entryName.name_length + 1]); + std::unique_ptr<char[]> entryNameCString(new char[entryName.name_length + 1]); memcpy(entryNameCString.get(), entryName.name, entryName.name_length); entryNameCString[entryName.name_length] = '\0'; + ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryNameCString.get())); - return newZipEntry(env, data, entryNameCString.get(), entryName.name_length); + return newZipEntry(env, data, entryNameString.get()); } static jobject StrictJarFile_nativeFindEntry(JNIEnv* env, jobject, jlong nativeHandle, @@ -172,12 +140,12 @@ static jobject StrictJarFile_nativeFindEntry(JNIEnv* env, jobject, jlong nativeH ZipEntry data; const int32_t error = FindEntry(reinterpret_cast<ZipArchiveHandle>(nativeHandle), - entryNameChars.c_str(), &data); + ZipEntryName(entryNameChars.c_str()), &data); if (error) { return NULL; } - return newZipEntry(env, data, entryName, entryNameChars.size()); + return newZipEntry(env, data, entryName); } static void StrictJarFile_nativeClose(JNIEnv*, jobject, jlong nativeHandle) { @@ -194,4 +162,8 @@ static JNINativeMethod gMethods[] = { void register_java_util_jar_StrictJarFile(JNIEnv* env) { jniRegisterNativeMethods(env, "java/util/jar/StrictJarFile", gMethods, NELEM(gMethods)); + + zipEntryCtor = env->GetMethodID(JniConstants::zipEntryClass, "<init>", + "(Ljava/lang/String;Ljava/lang/String;JJJIII[BJJ)V"); + LOG_ALWAYS_FATAL_IF(zipEntryCtor == NULL, "Unable to find ZipEntry.<init>"); } diff --git a/luni/src/main/native/java_util_regex_Matcher.cpp b/luni/src/main/native/java_util_regex_Matcher.cpp index 2e5259e..35d014c 100644 --- a/luni/src/main/native/java_util_regex_Matcher.cpp +++ b/luni/src/main/native/java_util_regex_Matcher.cpp @@ -23,15 +23,14 @@ #include "JniConstants.h" #include "JniException.h" #include "ScopedPrimitiveArray.h" -#include "UniquePtr.h" #include "jni.h" #include "unicode/parseerr.h" #include "unicode/regex.h" // ICU documentation: http://icu-project.org/apiref/icu4c/classRegexMatcher.html -static RegexMatcher* toRegexMatcher(jlong address) { - return reinterpret_cast<RegexMatcher*>(static_cast<uintptr_t>(address)); +static icu::RegexMatcher* toRegexMatcher(jlong address) { + return reinterpret_cast<icu::RegexMatcher*>(static_cast<uintptr_t>(address)); } /** @@ -75,7 +74,7 @@ public: maybeThrowIcuException(mEnv, "utext_close", mStatus); } - RegexMatcher* operator->() { + icu::RegexMatcher* operator->() { return mMatcher; } @@ -107,7 +106,7 @@ private: JNIEnv* mEnv; jstring mJavaInput; - RegexMatcher* mMatcher; + icu::RegexMatcher* mMatcher; const jchar* mChars; UErrorCode mStatus; UText* mUText; @@ -171,9 +170,9 @@ static jint Matcher_matchesImpl(JNIEnv* env, jclass, jlong addr, jstring javaTex } static jlong Matcher_openImpl(JNIEnv* env, jclass, jlong patternAddr) { - RegexPattern* pattern = reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(patternAddr)); + icu::RegexPattern* pattern = reinterpret_cast<icu::RegexPattern*>(static_cast<uintptr_t>(patternAddr)); UErrorCode status = U_ZERO_ERROR; - RegexMatcher* result = pattern->matcher(status); + icu::RegexMatcher* result = pattern->matcher(status); maybeThrowIcuException(env, "RegexPattern::matcher", status); return reinterpret_cast<uintptr_t>(result); } diff --git a/luni/src/main/native/java_util_regex_Pattern.cpp b/luni/src/main/native/java_util_regex_Pattern.cpp index 1a99d0a..f2c07dc 100644 --- a/luni/src/main/native/java_util_regex_Pattern.cpp +++ b/luni/src/main/native/java_util_regex_Pattern.cpp @@ -27,8 +27,8 @@ // ICU documentation: http://icu-project.org/apiref/icu4c/classRegexPattern.html -static RegexPattern* toRegexPattern(jlong addr) { - return reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(addr)); +static icu::RegexPattern* toRegexPattern(jlong addr) { + return reinterpret_cast<icu::RegexPattern*>(static_cast<uintptr_t>(addr)); } static const char* regexDetailMessage(UErrorCode status) { @@ -86,8 +86,8 @@ static jlong Pattern_compileImpl(JNIEnv* env, jclass, jstring javaRegex, jint fl if (!regex.valid()) { return 0; } - UnicodeString& regexString(regex.unicodeString()); - RegexPattern* result = RegexPattern::compile(regexString, flags, error, status); + icu::UnicodeString& regexString(regex.unicodeString()); + icu::RegexPattern* result = icu::RegexPattern::compile(regexString, flags, error, status); if (!U_SUCCESS(status)) { throwPatternSyntaxException(env, status, javaRegex, error); } diff --git a/luni/src/main/native/java_util_zip_Deflater.cpp b/luni/src/main/native/java_util_zip_Deflater.cpp index 1afd36e..d963824 100644 --- a/luni/src/main/native/java_util_zip_Deflater.cpp +++ b/luni/src/main/native/java_util_zip_Deflater.cpp @@ -28,11 +28,11 @@ static void Deflater_setDictionaryImpl(JNIEnv* env, jobject, jbyteArray dict, in } static jlong Deflater_getTotalInImpl(JNIEnv*, jobject, jlong handle) { - return toNativeZipStream(handle)->stream.total_in; + return toNativeZipStream(handle)->totalIn; } static jlong Deflater_getTotalOutImpl(JNIEnv*, jobject, jlong handle) { - return toNativeZipStream(handle)->stream.total_out; + return toNativeZipStream(handle)->totalOut; } static jint Deflater_getAdlerImpl(JNIEnv*, jobject, jlong handle) { @@ -40,7 +40,7 @@ static jint Deflater_getAdlerImpl(JNIEnv*, jobject, jlong handle) { } static jlong Deflater_createStream(JNIEnv * env, jobject, jint level, jint strategy, jboolean noHeader) { - UniquePtr<NativeZipStream> jstream(new NativeZipStream); + std::unique_ptr<NativeZipStream> jstream(new NativeZipStream); if (jstream.get() == NULL) { jniThrowOutOfMemoryError(env, NULL); return -1; @@ -101,6 +101,9 @@ static jint Deflater_deflateImpl(JNIEnv* env, jobject recv, jbyteArray buf, int jint bytesRead = stream->stream.next_in - initialNextIn; jint bytesWritten = stream->stream.next_out - initialNextOut; + stream->totalIn += bytesRead; + stream->totalOut += bytesWritten; + static jfieldID inReadField = env->GetFieldID(JniConstants::deflaterClass, "inRead", "I"); jint inReadValue = env->GetIntField(recv, inReadField); inReadValue += bytesRead; @@ -116,6 +119,8 @@ static void Deflater_endImpl(JNIEnv*, jobject, jlong handle) { static void Deflater_resetImpl(JNIEnv* env, jobject, jlong handle) { NativeZipStream* stream = toNativeZipStream(handle); + stream->totalIn = 0; + stream->totalOut = 0; int err = deflateReset(&stream->stream); if (err != Z_OK) { throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err, stream); diff --git a/luni/src/main/native/java_util_zip_Inflater.cpp b/luni/src/main/native/java_util_zip_Inflater.cpp index ca3ee09..f0878ff 100644 --- a/luni/src/main/native/java_util_zip_Inflater.cpp +++ b/luni/src/main/native/java_util_zip_Inflater.cpp @@ -25,7 +25,7 @@ #include <errno.h> static jlong Inflater_createStream(JNIEnv* env, jobject, jboolean noHeader) { - UniquePtr<NativeZipStream> jstream(new NativeZipStream); + std::unique_ptr<NativeZipStream> jstream(new NativeZipStream); if (jstream.get() == NULL) { jniThrowOutOfMemoryError(env, NULL); return -1; @@ -122,6 +122,9 @@ static jint Inflater_inflateImpl(JNIEnv* env, jobject recv, jbyteArray buf, int jint bytesRead = stream->stream.next_in - initialNextIn; jint bytesWritten = stream->stream.next_out - initialNextOut; + stream->totalIn += bytesRead; + stream->totalOut += bytesWritten; + static jfieldID inReadField = env->GetFieldID(JniConstants::inflaterClass, "inRead", "I"); jint inReadValue = env->GetIntField(recv, inReadField); inReadValue += bytesRead; @@ -145,6 +148,8 @@ static void Inflater_setDictionaryImpl(JNIEnv* env, jobject, jbyteArray dict, in static void Inflater_resetImpl(JNIEnv* env, jobject, jlong handle) { NativeZipStream* stream = toNativeZipStream(handle); + stream->totalIn = 0; + stream->totalOut = 0; int err = inflateReset(&stream->stream); if (err != Z_OK) { throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err, stream); @@ -152,11 +157,11 @@ static void Inflater_resetImpl(JNIEnv* env, jobject, jlong handle) { } static jlong Inflater_getTotalOutImpl(JNIEnv*, jobject, jlong handle) { - return toNativeZipStream(handle)->stream.total_out; + return toNativeZipStream(handle)->totalOut; } static jlong Inflater_getTotalInImpl(JNIEnv*, jobject, jlong handle) { - return toNativeZipStream(handle)->stream.total_in; + return toNativeZipStream(handle)->totalIn; } static JNINativeMethod gMethods[] = { diff --git a/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp b/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp index e0638bd..acc247b 100644 --- a/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp +++ b/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp @@ -25,8 +25,8 @@ #include "unicode/alphaindex.h" #include "unicode/uniset.h" -static AlphabeticIndex* fromPeer(jlong peer) { - return reinterpret_cast<AlphabeticIndex*>(static_cast<uintptr_t>(peer)); +static icu::AlphabeticIndex* fromPeer(jlong peer) { + return reinterpret_cast<icu::AlphabeticIndex*>(static_cast<uintptr_t>(peer)); } static jlong AlphabeticIndex_create(JNIEnv* env, jclass, jstring javaLocaleName) { @@ -35,7 +35,7 @@ static jlong AlphabeticIndex_create(JNIEnv* env, jclass, jstring javaLocaleName) if (!icuLocale.valid()) { return 0; } - AlphabeticIndex* ai = new AlphabeticIndex(icuLocale.locale(), status); + icu::AlphabeticIndex* ai = new icu::AlphabeticIndex(icuLocale.locale(), status); if (maybeThrowIcuException(env, "AlphabeticIndex", status)) { return 0; } @@ -47,19 +47,19 @@ static void AlphabeticIndex_destroy(JNIEnv*, jclass, jlong peer) { } static jint AlphabeticIndex_getMaxLabelCount(JNIEnv*, jclass, jlong peer) { - AlphabeticIndex* ai = fromPeer(peer); + icu::AlphabeticIndex* ai = fromPeer(peer); return ai->getMaxLabelCount(); } static void AlphabeticIndex_setMaxLabelCount(JNIEnv* env, jclass, jlong peer, jint count) { - AlphabeticIndex* ai = fromPeer(peer); + icu::AlphabeticIndex* ai = fromPeer(peer); UErrorCode status = U_ZERO_ERROR; ai->setMaxLabelCount(count, status); maybeThrowIcuException(env, "AlphabeticIndex::setMaxLabelCount", status); } static void AlphabeticIndex_addLabels(JNIEnv* env, jclass, jlong peer, jstring javaLocaleName) { - AlphabeticIndex* ai = fromPeer(peer); + icu::AlphabeticIndex* ai = fromPeer(peer); ScopedIcuLocale icuLocale(env, javaLocaleName); if (!icuLocale.valid()) { return; @@ -71,14 +71,14 @@ static void AlphabeticIndex_addLabels(JNIEnv* env, jclass, jlong peer, jstring j static void AlphabeticIndex_addLabelRange(JNIEnv* env, jclass, jlong peer, jint codePointStart, jint codePointEnd) { - AlphabeticIndex* ai = fromPeer(peer); + icu::AlphabeticIndex* ai = fromPeer(peer); UErrorCode status = U_ZERO_ERROR; - ai->addLabels(UnicodeSet(codePointStart, codePointEnd), status); + ai->addLabels(icu::UnicodeSet(codePointStart, codePointEnd), status); maybeThrowIcuException(env, "AlphabeticIndex::addLabels", status); } static jint AlphabeticIndex_getBucketCount(JNIEnv* env, jclass, jlong peer) { - AlphabeticIndex* ai = fromPeer(peer); + icu::AlphabeticIndex* ai = fromPeer(peer); UErrorCode status = U_ZERO_ERROR; jint result = ai->getBucketCount(status); if (maybeThrowIcuException(env, "AlphabeticIndex::getBucketCount", status)) { @@ -88,7 +88,7 @@ static jint AlphabeticIndex_getBucketCount(JNIEnv* env, jclass, jlong peer) { } static jint AlphabeticIndex_getBucketIndex(JNIEnv* env, jclass, jlong peer, jstring javaString) { - AlphabeticIndex* ai = fromPeer(peer); + icu::AlphabeticIndex* ai = fromPeer(peer); ScopedJavaUnicodeString string(env, javaString); if (!string.valid()) { return -1; @@ -108,7 +108,7 @@ static jstring AlphabeticIndex_getBucketLabel(JNIEnv* env, jclass, jlong peer, j } // Iterate to the nth bucket. - AlphabeticIndex* ai = fromPeer(peer); + icu::AlphabeticIndex* ai = fromPeer(peer); UErrorCode status = U_ZERO_ERROR; ai->resetBucketIterator(status); if (maybeThrowIcuException(env, "AlphabeticIndex::resetBucketIterator", status)) { @@ -129,31 +129,31 @@ static jstring AlphabeticIndex_getBucketLabel(JNIEnv* env, jclass, jlong peer, j return env->NewStringUTF(""); } - const UnicodeString& label(ai->getBucketLabel()); + const icu::UnicodeString& label(ai->getBucketLabel()); return env->NewString(label.getBuffer(), label.length()); } static jlong AlphabeticIndex_buildImmutableIndex(JNIEnv* env, jclass, jlong peer) { - AlphabeticIndex* ai = fromPeer(peer); + icu::AlphabeticIndex* ai = fromPeer(peer); UErrorCode status = U_ZERO_ERROR; - AlphabeticIndex::ImmutableIndex* ii = ai->buildImmutableIndex(status); + icu::AlphabeticIndex::ImmutableIndex* ii = ai->buildImmutableIndex(status); if (maybeThrowIcuException(env, "AlphabeticIndex::buildImmutableIndex", status)) { return 0; } return reinterpret_cast<uintptr_t>(ii); } -static AlphabeticIndex::ImmutableIndex* immutableIndexFromPeer(jlong peer) { - return reinterpret_cast<AlphabeticIndex::ImmutableIndex*>(static_cast<uintptr_t>(peer)); +static icu::AlphabeticIndex::ImmutableIndex* immutableIndexFromPeer(jlong peer) { + return reinterpret_cast<icu::AlphabeticIndex::ImmutableIndex*>(static_cast<uintptr_t>(peer)); } static jint ImmutableIndex_getBucketCount(JNIEnv*, jclass, jlong peer) { - AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer); + icu::AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer); return ii->getBucketCount(); } static jint ImmutableIndex_getBucketIndex(JNIEnv* env, jclass, jlong peer, jstring javaString) { - AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer); + icu::AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer); ScopedJavaUnicodeString string(env, javaString); if (!string.valid()) { return -1; @@ -167,8 +167,8 @@ static jint ImmutableIndex_getBucketIndex(JNIEnv* env, jclass, jlong peer, jstri } static jstring ImmutableIndex_getBucketLabel(JNIEnv* env, jclass, jlong peer, jint index) { - AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer); - const AlphabeticIndex::Bucket* bucket = ii->getBucket(index); + icu::AlphabeticIndex::ImmutableIndex* ii = immutableIndexFromPeer(peer); + const icu::AlphabeticIndex::Bucket* bucket = ii->getBucket(index); if (bucket == NULL) { jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid index: %d", index); return NULL; @@ -179,7 +179,7 @@ static jstring ImmutableIndex_getBucketLabel(JNIEnv* env, jclass, jlong peer, ji return env->NewStringUTF(""); } - const UnicodeString& label(bucket->getLabel()); + const icu::UnicodeString& label(bucket->getLabel()); return env->NewString(label.getBuffer(), label.length()); } diff --git a/luni/src/main/native/libcore_icu_DateIntervalFormat.cpp b/luni/src/main/native/libcore_icu_DateIntervalFormat.cpp deleted file mode 100644 index a3258c1..0000000 --- a/luni/src/main/native/libcore_icu_DateIntervalFormat.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2013 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. - */ - -#define LOG_TAG "DateIntervalFormat" - -#include "IcuUtilities.h" -#include "JniConstants.h" -#include "ScopedIcuLocale.h" -#include "ScopedJavaUnicodeString.h" -#include "UniquePtr.h" -#include "cutils/log.h" -#include "unicode/dtitvfmt.h" - -static jlong DateIntervalFormat_createDateIntervalFormat(JNIEnv* env, jclass, jstring javaSkeleton, jstring javaLocaleName, jstring javaTzName) { - ScopedIcuLocale icuLocale(env, javaLocaleName); - if (!icuLocale.valid()) { - return 0; - } - - ScopedJavaUnicodeString skeletonHolder(env, javaSkeleton); - if (!skeletonHolder.valid()) { - return 0; - } - - UErrorCode status = U_ZERO_ERROR; - DateIntervalFormat* formatter(DateIntervalFormat::createInstance(skeletonHolder.unicodeString(), icuLocale.locale(), status)); - if (maybeThrowIcuException(env, "DateIntervalFormat::createInstance", status)) { - return 0; - } - - ScopedJavaUnicodeString tzNameHolder(env, javaTzName); - if (!tzNameHolder.valid()) { - return 0; - } - formatter->adoptTimeZone(TimeZone::createTimeZone(tzNameHolder.unicodeString())); - - return reinterpret_cast<uintptr_t>(formatter); -} - -static void DateIntervalFormat_destroyDateIntervalFormat(JNIEnv*, jclass, jlong address) { - delete reinterpret_cast<DateIntervalFormat*>(address); -} - -static jstring DateIntervalFormat_formatDateInterval(JNIEnv* env, jclass, jlong address, jlong fromDate, jlong toDate) { - DateIntervalFormat* formatter(reinterpret_cast<DateIntervalFormat*>(address)); - DateInterval date_interval(fromDate, toDate); - - UnicodeString s; - FieldPosition pos = 0; - UErrorCode status = U_ZERO_ERROR; - formatter->format(&date_interval, s, pos, status); - if (maybeThrowIcuException(env, "DateIntervalFormat::format", status)) { - return NULL; - } - - return env->NewString(s.getBuffer(), s.length()); -} - -static JNINativeMethod gMethods[] = { - NATIVE_METHOD(DateIntervalFormat, createDateIntervalFormat, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J"), - NATIVE_METHOD(DateIntervalFormat, destroyDateIntervalFormat, "(J)V"), - NATIVE_METHOD(DateIntervalFormat, formatDateInterval, "(JJJ)Ljava/lang/String;"), -}; -void register_libcore_icu_DateIntervalFormat(JNIEnv* env) { - jniRegisterNativeMethods(env, "libcore/icu/DateIntervalFormat", gMethods, NELEM(gMethods)); -} diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp index d27b11d..0e744b7 100644 --- a/luni/src/main/native/libcore_icu_ICU.cpp +++ b/luni/src/main/native/libcore_icu_ICU.cpp @@ -25,7 +25,6 @@ #include "ScopedJavaUnicodeString.h" #include "ScopedLocalRef.h" #include "ScopedUtfChars.h" -#include "UniquePtr.h" #include "cutils/log.h" #include "toStringArray.h" #include "unicode/brkiter.h" @@ -39,6 +38,7 @@ #include "unicode/locid.h" #include "unicode/numfmt.h" #include "unicode/strenum.h" +#include "unicode/timezone.h" #include "unicode/ubrk.h" #include "unicode/ucal.h" #include "unicode/uclean.h" @@ -62,15 +62,9 @@ #include <sys/types.h> #include <time.h> #include <unistd.h> +#include <memory> #include <vector> -// TODO: put this in a header file and use it everywhere! -// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. -// It goes in the private: declarations in a class. -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) - class ScopedResourceBundle { public: ScopedResourceBundle(UResourceBundle* bundle) : bundle_(bundle) { @@ -121,7 +115,7 @@ static jint ICU_getCurrencyFractionDigits(JNIEnv* env, jclass, jstring javaCurre if (!currencyCode.valid()) { return 0; } - UnicodeString icuCurrencyCode(currencyCode.unicodeString()); + icu::UnicodeString icuCurrencyCode(currencyCode.unicodeString()); UErrorCode status = U_ZERO_ERROR; return ucurr_getDefaultFractionDigits(icuCurrencyCode.getTerminatedBuffer(), &status); } @@ -131,7 +125,7 @@ static jint ICU_getCurrencyNumericCode(JNIEnv* env, jclass, jstring javaCurrency if (!currencyCode.valid()) { return 0; } - UnicodeString icuCurrencyCode(currencyCode.unicodeString()); + icu::UnicodeString icuCurrencyCode(currencyCode.unicodeString()); return ucurr_getNumericCode(icuCurrencyCode.getTerminatedBuffer()); } @@ -187,7 +181,7 @@ static jstring getCurrencyName(JNIEnv* env, jstring javaLanguageTag, jstring jav if (!currencyCode.valid()) { return NULL; } - UnicodeString icuCurrencyCode(currencyCode.unicodeString()); + icu::UnicodeString icuCurrencyCode(currencyCode.unicodeString()); UErrorCode status = U_ZERO_ERROR; UBool isChoiceFormat = false; int32_t charCount; @@ -228,7 +222,7 @@ static jstring ICU_getDisplayCountryNative(JNIEnv* env, jclass, jstring javaTarg return NULL; } - UnicodeString str; + icu::UnicodeString str; icuTargetLocale.locale().getDisplayCountry(icuLocale.locale(), str); return env->NewString(str.getBuffer(), str.length()); } @@ -243,7 +237,7 @@ static jstring ICU_getDisplayLanguageNative(JNIEnv* env, jclass, jstring javaTar return NULL; } - UnicodeString str; + icu::UnicodeString str; icuTargetLocale.locale().getDisplayLanguage(icuLocale.locale(), str); return env->NewString(str.getBuffer(), str.length()); } @@ -258,7 +252,7 @@ static jstring ICU_getDisplayScriptNative(JNIEnv* env, jclass, jstring javaTarge return NULL; } - UnicodeString str; + icu::UnicodeString str; icuTargetLocale.locale().getDisplayScript(icuLocale.locale(), str); return env->NewString(str.getBuffer(), str.length()); } @@ -273,7 +267,7 @@ static jstring ICU_getDisplayVariantNative(JNIEnv* env, jclass, jstring javaTarg return NULL; } - UnicodeString str; + icu::UnicodeString str; icuTargetLocale.locale().getDisplayVariant(icuLocale.locale(), str); return env->NewString(str.getBuffer(), str.length()); } @@ -295,11 +289,11 @@ static jstring ICU_getISO3Language(JNIEnv* env, jclass, jstring javaLanguageTag) } static jobjectArray ICU_getISOCountriesNative(JNIEnv* env, jclass) { - return toStringArray(env, Locale::getISOCountries()); + return toStringArray(env, icu::Locale::getISOCountries()); } static jobjectArray ICU_getISOLanguagesNative(JNIEnv* env, jclass) { - return toStringArray(env, Locale::getISOLanguages()); + return toStringArray(env, icu::Locale::getISOLanguages()); } static jobjectArray ICU_getAvailableLocalesNative(JNIEnv* env, jclass) { @@ -343,7 +337,7 @@ static void setStringArrayField(JNIEnv* env, jobject obj, const char* fieldName, env->SetObjectField(obj, fid, value); } -static void setStringArrayField(JNIEnv* env, jobject obj, const char* fieldName, const UnicodeString* valueArray, int32_t size) { +static void setStringArrayField(JNIEnv* env, jobject obj, const char* fieldName, const icu::UnicodeString* valueArray, int32_t size) { ScopedLocalRef<jobjectArray> result(env, env->NewObjectArray(size, JniConstants::stringClass, NULL)); for (int32_t i = 0; i < size ; i++) { ScopedLocalRef<jstring> s(env, env->NewString(valueArray[i].getBuffer(),valueArray[i].length())); @@ -369,7 +363,7 @@ static void setStringField(JNIEnv* env, jobject obj, const char* fieldName, URes } } -static void setCharField(JNIEnv* env, jobject obj, const char* fieldName, const UnicodeString& value) { +static void setCharField(JNIEnv* env, jobject obj, const char* fieldName, const icu::UnicodeString& value) { if (value.length() == 0) { return; } @@ -377,43 +371,43 @@ static void setCharField(JNIEnv* env, jobject obj, const char* fieldName, const env->SetCharField(obj, fid, value.charAt(0)); } -static void setStringField(JNIEnv* env, jobject obj, const char* fieldName, const UnicodeString& value) { +static void setStringField(JNIEnv* env, jobject obj, const char* fieldName, const icu::UnicodeString& value) { const UChar* chars = value.getBuffer(); setStringField(env, obj, fieldName, env->NewString(chars, value.length())); } -static void setNumberPatterns(JNIEnv* env, jobject obj, Locale& locale) { +static void setNumberPatterns(JNIEnv* env, jobject obj, icu::Locale& locale) { UErrorCode status = U_ZERO_ERROR; - UnicodeString pattern; - UniquePtr<DecimalFormat> fmt(static_cast<DecimalFormat*>(NumberFormat::createInstance(locale, UNUM_CURRENCY, status))); + icu::UnicodeString pattern; + std::unique_ptr<icu::DecimalFormat> fmt(static_cast<icu::DecimalFormat*>(icu::NumberFormat::createInstance(locale, UNUM_CURRENCY, status))); pattern = fmt->toPattern(pattern.remove()); setStringField(env, obj, "currencyPattern", pattern); - fmt.reset(static_cast<DecimalFormat*>(NumberFormat::createInstance(locale, UNUM_DECIMAL, status))); + fmt.reset(static_cast<icu::DecimalFormat*>(icu::NumberFormat::createInstance(locale, UNUM_DECIMAL, status))); pattern = fmt->toPattern(pattern.remove()); setStringField(env, obj, "numberPattern", pattern); - fmt.reset(static_cast<DecimalFormat*>(NumberFormat::createInstance(locale, UNUM_PERCENT, status))); + fmt.reset(static_cast<icu::DecimalFormat*>(icu::NumberFormat::createInstance(locale, UNUM_PERCENT, status))); pattern = fmt->toPattern(pattern.remove()); setStringField(env, obj, "percentPattern", pattern); } -static void setDecimalFormatSymbolsData(JNIEnv* env, jobject obj, Locale& locale) { +static void setDecimalFormatSymbolsData(JNIEnv* env, jobject obj, icu::Locale& locale) { UErrorCode status = U_ZERO_ERROR; - DecimalFormatSymbols dfs(locale, status); + icu::DecimalFormatSymbols dfs(locale, status); - setCharField(env, obj, "decimalSeparator", dfs.getSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol)); - setCharField(env, obj, "groupingSeparator", dfs.getSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol)); - setCharField(env, obj, "patternSeparator", dfs.getSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol)); - setStringField(env, obj, "percent", dfs.getSymbol(DecimalFormatSymbols::kPercentSymbol)); - setCharField(env, obj, "perMill", dfs.getSymbol(DecimalFormatSymbols::kPerMillSymbol)); - setCharField(env, obj, "monetarySeparator", dfs.getSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol)); - setStringField(env, obj, "minusSign", dfs.getSymbol(DecimalFormatSymbols:: kMinusSignSymbol)); - setStringField(env, obj, "exponentSeparator", dfs.getSymbol(DecimalFormatSymbols::kExponentialSymbol)); - setStringField(env, obj, "infinity", dfs.getSymbol(DecimalFormatSymbols::kInfinitySymbol)); - setStringField(env, obj, "NaN", dfs.getSymbol(DecimalFormatSymbols::kNaNSymbol)); - setCharField(env, obj, "zeroDigit", dfs.getSymbol(DecimalFormatSymbols::kZeroDigitSymbol)); + setCharField(env, obj, "decimalSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kDecimalSeparatorSymbol)); + setCharField(env, obj, "groupingSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kGroupingSeparatorSymbol)); + setCharField(env, obj, "patternSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kPatternSeparatorSymbol)); + setStringField(env, obj, "percent", dfs.getSymbol(icu::DecimalFormatSymbols::kPercentSymbol)); + setCharField(env, obj, "perMill", dfs.getSymbol(icu::DecimalFormatSymbols::kPerMillSymbol)); + setCharField(env, obj, "monetarySeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kMonetarySeparatorSymbol)); + setStringField(env, obj, "minusSign", dfs.getSymbol(icu::DecimalFormatSymbols:: kMinusSignSymbol)); + setStringField(env, obj, "exponentSeparator", dfs.getSymbol(icu::DecimalFormatSymbols::kExponentialSymbol)); + setStringField(env, obj, "infinity", dfs.getSymbol(icu::DecimalFormatSymbols::kInfinitySymbol)); + setStringField(env, obj, "NaN", dfs.getSymbol(icu::DecimalFormatSymbols::kNaNSymbol)); + setCharField(env, obj, "zeroDigit", dfs.getSymbol(icu::DecimalFormatSymbols::kZeroDigitSymbol)); } @@ -502,7 +496,7 @@ static bool getDateTimePatterns(JNIEnv* env, jobject localeData, const char* loc return true; } -static bool getYesterdayTodayAndTomorrow(JNIEnv* env, jobject localeData, const Locale& locale, const char* locale_name) { +static bool getYesterdayTodayAndTomorrow(JNIEnv* env, jobject localeData, const icu::Locale& locale, const char* locale_name) { UErrorCode status = U_ZERO_ERROR; ScopedResourceBundle root(ures_open(NULL, locale_name, &status)); ScopedResourceBundle fields(ures_getByKey(root.get(), "fields", NULL, &status)); @@ -512,16 +506,16 @@ static bool getYesterdayTodayAndTomorrow(JNIEnv* env, jobject localeData, const return false; } - UnicodeString yesterday(ures_getUnicodeStringByKey(relative.get(), "-1", &status)); - UnicodeString today(ures_getUnicodeStringByKey(relative.get(), "0", &status)); - UnicodeString tomorrow(ures_getUnicodeStringByKey(relative.get(), "1", &status)); + icu::UnicodeString yesterday(icu::ures_getUnicodeStringByKey(relative.get(), "-1", &status)); + icu::UnicodeString today(icu::ures_getUnicodeStringByKey(relative.get(), "0", &status)); + icu::UnicodeString tomorrow(icu::ures_getUnicodeStringByKey(relative.get(), "1", &status)); if (U_FAILURE(status)) { ALOGE("Error getting yesterday/today/tomorrow for %s: %s", locale_name, u_errorName(status)); return false; } // We title-case the strings so they have consistent capitalization (http://b/14493853). - UniquePtr<BreakIterator> brk(BreakIterator::createSentenceInstance(locale, status)); + std::unique_ptr<icu::BreakIterator> brk(icu::BreakIterator::createSentenceInstance(locale, status)); if (U_FAILURE(status)) { ALOGE("Error getting yesterday/today/tomorrow break iterator for %s: %s", locale_name, u_errorName(status)); return false; @@ -591,7 +585,7 @@ static jboolean ICU_initLocaleDataNative(JNIEnv* env, jclass, jstring javaLangua } status = U_ZERO_ERROR; - UniquePtr<Calendar> cal(Calendar::createInstance(icuLocale.locale(), status)); + std::unique_ptr<icu::Calendar> cal(icu::Calendar::createInstance(icuLocale.locale(), status)); if (U_FAILURE(status)) { return JNI_FALSE; } @@ -601,54 +595,54 @@ static jboolean ICU_initLocaleDataNative(JNIEnv* env, jclass, jstring javaLangua // Get DateFormatSymbols. status = U_ZERO_ERROR; - DateFormatSymbols dateFormatSym(icuLocale.locale(), status); + icu::DateFormatSymbols dateFormatSym(icuLocale.locale(), status); if (U_FAILURE(status)) { return JNI_FALSE; } // Get AM/PM and BC/AD. int32_t count = 0; - const UnicodeString* amPmStrs = dateFormatSym.getAmPmStrings(count); + const icu::UnicodeString* amPmStrs = dateFormatSym.getAmPmStrings(count); setStringArrayField(env, localeData, "amPm", amPmStrs, count); - const UnicodeString* erasStrs = dateFormatSym.getEras(count); + const icu::UnicodeString* erasStrs = dateFormatSym.getEras(count); setStringArrayField(env, localeData, "eras", erasStrs, count); - const UnicodeString* longMonthNames = - dateFormatSym.getMonths(count, DateFormatSymbols::FORMAT, DateFormatSymbols::WIDE); + const icu::UnicodeString* longMonthNames = + dateFormatSym.getMonths(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::WIDE); setStringArrayField(env, localeData, "longMonthNames", longMonthNames, count); - const UnicodeString* shortMonthNames = - dateFormatSym.getMonths(count, DateFormatSymbols::FORMAT, DateFormatSymbols::ABBREVIATED); + const icu::UnicodeString* shortMonthNames = + dateFormatSym.getMonths(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::ABBREVIATED); setStringArrayField(env, localeData, "shortMonthNames", shortMonthNames, count); - const UnicodeString* tinyMonthNames = - dateFormatSym.getMonths(count, DateFormatSymbols::FORMAT, DateFormatSymbols::NARROW); + const icu::UnicodeString* tinyMonthNames = + dateFormatSym.getMonths(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::NARROW); setStringArrayField(env, localeData, "tinyMonthNames", tinyMonthNames, count); - const UnicodeString* longWeekdayNames = - dateFormatSym.getWeekdays(count, DateFormatSymbols::FORMAT, DateFormatSymbols::WIDE); + const icu::UnicodeString* longWeekdayNames = + dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::WIDE); setStringArrayField(env, localeData, "longWeekdayNames", longWeekdayNames, count); - const UnicodeString* shortWeekdayNames = - dateFormatSym.getWeekdays(count, DateFormatSymbols::FORMAT, DateFormatSymbols::ABBREVIATED); + const icu::UnicodeString* shortWeekdayNames = + dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::ABBREVIATED); setStringArrayField(env, localeData, "shortWeekdayNames", shortWeekdayNames, count); - const UnicodeString* tinyWeekdayNames = - dateFormatSym.getWeekdays(count, DateFormatSymbols::FORMAT, DateFormatSymbols::NARROW); + const icu::UnicodeString* tinyWeekdayNames = + dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::FORMAT, icu::DateFormatSymbols::NARROW); setStringArrayField(env, localeData, "tinyWeekdayNames", tinyWeekdayNames, count); - const UnicodeString* longStandAloneMonthNames = - dateFormatSym.getMonths(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::WIDE); + const icu::UnicodeString* longStandAloneMonthNames = + dateFormatSym.getMonths(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::WIDE); setStringArrayField(env, localeData, "longStandAloneMonthNames", longStandAloneMonthNames, count); - const UnicodeString* shortStandAloneMonthNames = - dateFormatSym.getMonths(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::ABBREVIATED); + const icu::UnicodeString* shortStandAloneMonthNames = + dateFormatSym.getMonths(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::ABBREVIATED); setStringArrayField(env, localeData, "shortStandAloneMonthNames", shortStandAloneMonthNames, count); - const UnicodeString* tinyStandAloneMonthNames = - dateFormatSym.getMonths(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::NARROW); + const icu::UnicodeString* tinyStandAloneMonthNames = + dateFormatSym.getMonths(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::NARROW); setStringArrayField(env, localeData, "tinyStandAloneMonthNames", tinyStandAloneMonthNames, count); - const UnicodeString* longStandAloneWeekdayNames = - dateFormatSym.getWeekdays(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::WIDE); + const icu::UnicodeString* longStandAloneWeekdayNames = + dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::WIDE); setStringArrayField(env, localeData, "longStandAloneWeekdayNames", longStandAloneWeekdayNames, count); - const UnicodeString* shortStandAloneWeekdayNames = - dateFormatSym.getWeekdays(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::ABBREVIATED); + const icu::UnicodeString* shortStandAloneWeekdayNames = + dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::ABBREVIATED); setStringArrayField(env, localeData, "shortStandAloneWeekdayNames", shortStandAloneWeekdayNames, count); - const UnicodeString* tinyStandAloneWeekdayNames = - dateFormatSym.getWeekdays(count, DateFormatSymbols::STANDALONE, DateFormatSymbols::NARROW); + const icu::UnicodeString* tinyStandAloneWeekdayNames = + dateFormatSym.getWeekdays(count, icu::DateFormatSymbols::STANDALONE, icu::DateFormatSymbols::NARROW); setStringArrayField(env, localeData, "tinyStandAloneWeekdayNames", tinyStandAloneWeekdayNames, count); status = U_ZERO_ERROR; @@ -687,8 +681,8 @@ static jstring ICU_toLowerCase(JNIEnv* env, jclass, jstring javaString, jstring if (!icuLocale.valid()) { return NULL; } - UnicodeString& s(scopedString.unicodeString()); - UnicodeString original(s); + icu::UnicodeString& s(scopedString.unicodeString()); + icu::UnicodeString original(s); s.toLower(icuLocale.locale()); return s == original ? javaString : env->NewString(s.getBuffer(), s.length()); } @@ -702,8 +696,8 @@ static jstring ICU_toUpperCase(JNIEnv* env, jclass, jstring javaString, jstring if (!icuLocale.valid()) { return NULL; } - UnicodeString& s(scopedString.unicodeString()); - UnicodeString original(s); + icu::UnicodeString& s(scopedString.unicodeString()); + icu::UnicodeString original(s); s.toUpper(icuLocale.locale()); return s == original ? javaString : env->NewString(s.getBuffer(), s.length()); } @@ -733,9 +727,18 @@ static jstring ICU_getUnicodeVersion(JNIEnv* env, jclass) { return versionString(env, unicodeVersion); } +static jstring ICU_getTZDataVersion(JNIEnv* env, jclass) { + UErrorCode status = U_ZERO_ERROR; + const char* version = icu::TimeZone::getTZDataVersion(status); + if (maybeThrowIcuException(env, "icu::TimeZone::getTZDataVersion", status)) { + return NULL; + } + return env->NewStringUTF(version); +} + static jobject ICU_getAvailableCurrencyCodes(JNIEnv* env, jclass) { UErrorCode status = U_ZERO_ERROR; - UStringEnumeration e(ucurr_openISOCurrencies(UCURR_COMMON|UCURR_NON_DEPRECATED, &status)); + icu::UStringEnumeration e(ucurr_openISOCurrencies(UCURR_COMMON|UCURR_NON_DEPRECATED, &status)); return fromStringEnumeration(env, status, "ucurr_openISOCurrencies", &e); } @@ -746,7 +749,7 @@ static jstring ICU_getBestDateTimePatternNative(JNIEnv* env, jclass, jstring jav } UErrorCode status = U_ZERO_ERROR; - UniquePtr<DateTimePatternGenerator> generator(DateTimePatternGenerator::createInstance(icuLocale.locale(), status)); + std::unique_ptr<icu::DateTimePatternGenerator> generator(icu::DateTimePatternGenerator::createInstance(icuLocale.locale(), status)); if (maybeThrowIcuException(env, "DateTimePatternGenerator::createInstance", status)) { return NULL; } @@ -755,7 +758,7 @@ static jstring ICU_getBestDateTimePatternNative(JNIEnv* env, jclass, jstring jav if (!skeletonHolder.valid()) { return NULL; } - UnicodeString result(generator->getBestPattern(skeletonHolder.unicodeString(), status)); + icu::UnicodeString result(generator->getBestPattern(skeletonHolder.unicodeString(), status)); if (maybeThrowIcuException(env, "DateTimePatternGenerator::getBestPattern", status)) { return NULL; } @@ -770,12 +773,12 @@ static void ICU_setDefaultLocale(JNIEnv* env, jclass, jstring javaLanguageTag) { } UErrorCode status = U_ZERO_ERROR; - Locale::setDefault(icuLocale.locale(), status); + icu::Locale::setDefault(icuLocale.locale(), status); maybeThrowIcuException(env, "Locale::setDefault", status); } static jstring ICU_getDefaultLocale(JNIEnv* env, jclass) { - return env->NewStringUTF(Locale::getDefault().getName()); + return env->NewStringUTF(icu::Locale::getDefault().getName()); } static JNINativeMethod gMethods[] = { @@ -805,28 +808,25 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(ICU, getISOLanguagesNative, "()[Ljava/lang/String;"), NATIVE_METHOD(ICU, getIcuVersion, "()Ljava/lang/String;"), NATIVE_METHOD(ICU, getScript, "(Ljava/lang/String;)Ljava/lang/String;"), + NATIVE_METHOD(ICU, getTZDataVersion, "()Ljava/lang/String;"), NATIVE_METHOD(ICU, getUnicodeVersion, "()Ljava/lang/String;"), NATIVE_METHOD(ICU, initLocaleDataNative, "(Ljava/lang/String;Llibcore/icu/LocaleData;)Z"), NATIVE_METHOD(ICU, setDefaultLocale, "(Ljava/lang/String;)V"), NATIVE_METHOD(ICU, toLowerCase, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(ICU, toUpperCase, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"), }; -void register_libcore_icu_ICU(JNIEnv* env) { - std::string path; - path = u_getDataDirectory(); - path += "/"; - path += U_ICUDATA_NAME; - path += ".dat"; - - #define FAIL_WITH_STRERROR(s) \ - ALOGE("Couldn't " s " '%s': %s", path.c_str(), strerror(errno)); \ - abort(); - #define MAYBE_FAIL_WITH_ICU_ERROR(s) \ - if (status != U_ZERO_ERROR) {\ - ALOGE("Couldn't initialize ICU (" s "): %s (%s)", u_errorName(status), path.c_str()); \ - abort(); \ - } +#define FAIL_WITH_STRERROR(s) \ + ALOGE("Couldn't " s " '%s': %s", path.c_str(), strerror(errno)); \ + return FALSE; + +#define MAYBE_FAIL_WITH_ICU_ERROR(s) \ + if (status != U_ZERO_ERROR) {\ + ALOGE("Couldn't initialize ICU (" s "): %s (%s)", u_errorName(status), path.c_str()); \ + return FALSE; \ + } + +static bool mapIcuData(const std::string& path) { // Open the file and get its length. ScopedFd fd(open(path.c_str(), O_RDONLY)); if (fd.get() == -1) { @@ -848,18 +848,66 @@ void register_libcore_icu_ICU(JNIEnv* env) { FAIL_WITH_STRERROR("madvise(MADV_RANDOM)"); } - // Tell ICU to use our memory-mapped data. UErrorCode status = U_ZERO_ERROR; + + // Tell ICU to use our memory-mapped data. udata_setCommonData(data, &status); MAYBE_FAIL_WITH_ICU_ERROR("udata_setCommonData"); + + return TRUE; +} + +void register_libcore_icu_ICU(JNIEnv* env) { + // Check the timezone override file exists. If it does, map it first so we use it in preference + // to the one that shipped with the device. + const char* dataPathPrefix = getenv("ANDROID_DATA"); + if (dataPathPrefix == NULL) { + ALOGE("ANDROID_DATA environment variable not set"); \ + abort(); + } + + UErrorCode status = U_ZERO_ERROR; // Tell ICU it can *only* use our memory-mapped data. udata_setFileAccess(UDATA_NO_FILES, &status); - MAYBE_FAIL_WITH_ICU_ERROR("udata_setFileAccess"); + if (status != U_ZERO_ERROR) { + ALOGE("Couldn't initialize ICU (s_setFileAccess): %s", u_errorName(status)); + abort(); + } + + // Map in optional TZ data files. + std::string dataPath; + dataPath = dataPathPrefix; + dataPath += "/misc/zoneinfo/current/icu/icu_tzdata.dat"; + + struct stat sb; + if (stat(dataPath.c_str(), &sb) == 0) { + ALOGD("Timezone override file found: %s", dataPath.c_str()); + if (!mapIcuData(dataPath)) { + ALOGW("TZ override file %s exists but could not be loaded. Skipping.", dataPath.c_str()); + } + } else { + ALOGD("No timezone override file found: %s", dataPath.c_str()); + } + + // Use the ICU data files that shipped with the device for everything else. + std::string systemPath; + systemPath = u_getDataDirectory(); + systemPath += "/"; + systemPath += U_ICUDATA_NAME; + systemPath += ".dat"; + + if (!mapIcuData(systemPath)) { + abort(); + } // Failures to find the ICU data tend to be somewhat obscure because ICU loads its data on first // use, which can be anywhere. Force initialization up front so we can report a nice clear error // and bail. u_init(&status); - MAYBE_FAIL_WITH_ICU_ERROR("u_init"); + if (status != U_ZERO_ERROR) {\ + ALOGE("Couldn't initialize ICU (u_init): %s", u_errorName(status)); + abort(); + } + jniRegisterNativeMethods(env, "libcore/icu/ICU", gMethods, NELEM(gMethods)); } diff --git a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp deleted file mode 100644 index ef0c2a9..0000000 --- a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2006 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. - */ - -#define LOG_TAG "NativeBreakIterator" - -#include "IcuUtilities.h" -#include "JNIHelp.h" -#include "JniConstants.h" -#include "JniException.h" -#include "ScopedIcuLocale.h" -#include "ScopedUtfChars.h" -#include "unicode/brkiter.h" -#include "unicode/putil.h" -#include <stdlib.h> - -// ICU documentation: http://icu-project.org/apiref/icu4c/classBreakIterator.html - -static BreakIterator* toBreakIterator(jlong address) { - return reinterpret_cast<BreakIterator*>(static_cast<uintptr_t>(address)); -} - -/** - * We use ICU4C's BreakIterator class, but our input is on the Java heap and potentially moving - * around between calls. This wrapper class ensures that our RegexMatcher is always pointing at - * the current location of the char[]. Earlier versions of Android simply copied the data to the - * native heap, but that's wasteful and hides allocations from the garbage collector. - */ -class BreakIteratorAccessor { - public: - BreakIteratorAccessor(JNIEnv* env, jlong address, jstring javaInput, bool reset) { - init(env, address); - mJavaInput = javaInput; - - if (mJavaInput == NULL) { - return; - } - - mChars = env->GetStringChars(mJavaInput, NULL); - if (mChars == NULL) { - return; - } - - mUText = utext_openUChars(NULL, mChars, env->GetStringLength(mJavaInput), &mStatus); - if (mUText == NULL) { - return; - } - - if (reset) { - mBreakIterator->setText(mUText, mStatus); - } else { - mBreakIterator->refreshInputText(mUText, mStatus); - } - } - - BreakIteratorAccessor(JNIEnv* env, jlong address) { - init(env, address); - } - - ~BreakIteratorAccessor() { - utext_close(mUText); - if (mJavaInput) { - mEnv->ReleaseStringChars(mJavaInput, mChars); - } - maybeThrowIcuException(mEnv, "utext_close", mStatus); - } - - BreakIterator* operator->() { - return mBreakIterator; - } - - UErrorCode& status() { - return mStatus; - } - - private: - void init(JNIEnv* env, jlong address) { - mEnv = env; - mJavaInput = NULL; - mBreakIterator = toBreakIterator(address); - mChars = NULL; - mStatus = U_ZERO_ERROR; - mUText = NULL; - } - - JNIEnv* mEnv; - jstring mJavaInput; - BreakIterator* mBreakIterator; - const jchar* mChars; - UErrorCode mStatus; - UText* mUText; - - // Disallow copy and assignment. - BreakIteratorAccessor(const BreakIteratorAccessor&); - void operator=(const BreakIteratorAccessor&); -}; - -#define MAKE_BREAK_ITERATOR_INSTANCE(F) \ - ScopedIcuLocale icuLocale(env, javaLocaleName); \ - if (!icuLocale.valid()) { \ - return 0; \ - } \ - UErrorCode status = U_ZERO_ERROR; \ - BreakIterator* it = F(icuLocale.locale(), status); \ - if (maybeThrowIcuException(env, "ubrk_open", status)) { \ - return 0; \ - } \ - return reinterpret_cast<uintptr_t>(it) - -static jlong NativeBreakIterator_cloneImpl(JNIEnv* env, jclass, jlong address) { - BreakIteratorAccessor it(env, address); - return reinterpret_cast<uintptr_t>(it->clone()); -} - -static void NativeBreakIterator_closeImpl(JNIEnv*, jclass, jlong address) { - delete toBreakIterator(address); -} - -static jint NativeBreakIterator_currentImpl(JNIEnv* env, jclass, jlong address, jstring javaInput) { - BreakIteratorAccessor it(env, address, javaInput, false); - return it->current(); -} - -static jint NativeBreakIterator_firstImpl(JNIEnv* env, jclass, jlong address, jstring javaInput) { - BreakIteratorAccessor it(env, address, javaInput, false); - return it->first(); -} - -static jint NativeBreakIterator_followingImpl(JNIEnv* env, jclass, jlong address, jstring javaInput, jint offset) { - BreakIteratorAccessor it(env, address, javaInput, false); - return it->following(offset); -} - -static jlong NativeBreakIterator_getCharacterInstanceImpl(JNIEnv* env, jclass, jstring javaLocaleName) { - MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createCharacterInstance); -} - -static jlong NativeBreakIterator_getLineInstanceImpl(JNIEnv* env, jclass, jstring javaLocaleName) { - MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createLineInstance); -} - -static jlong NativeBreakIterator_getSentenceInstanceImpl(JNIEnv* env, jclass, jstring javaLocaleName) { - MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createSentenceInstance); -} - -static jlong NativeBreakIterator_getWordInstanceImpl(JNIEnv* env, jclass, jstring javaLocaleName) { - MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createWordInstance); -} - -static jboolean NativeBreakIterator_isBoundaryImpl(JNIEnv* env, jclass, jlong address, jstring javaInput, jint offset) { - BreakIteratorAccessor it(env, address, javaInput, false); - return it->isBoundary(offset); -} - -static jint NativeBreakIterator_lastImpl(JNIEnv* env, jclass, jlong address, jstring javaInput) { - BreakIteratorAccessor it(env, address, javaInput, false); - return it->last(); -} - -static jint NativeBreakIterator_nextImpl(JNIEnv* env, jclass, jlong address, jstring javaInput, jint n) { - BreakIteratorAccessor it(env, address, javaInput, false); - if (n < 0) { - while (n++ < -1) { - it->previous(); - } - return it->previous(); - } else if (n == 0) { - return it->current(); - } else { - while (n-- > 1) { - it->next(); - } - return it->next(); - } - return -1; -} - -static jint NativeBreakIterator_precedingImpl(JNIEnv* env, jclass, jlong address, jstring javaInput, jint offset) { - BreakIteratorAccessor it(env, address, javaInput, false); - return it->preceding(offset); -} - -static jint NativeBreakIterator_previousImpl(JNIEnv* env, jclass, jlong address, jstring javaInput) { - BreakIteratorAccessor it(env, address, javaInput, false); - return it->previous(); -} - -static void NativeBreakIterator_setTextImpl(JNIEnv* env, jclass, jlong address, jstring javaInput) { - BreakIteratorAccessor it(env, address, javaInput, true); -} - -static JNINativeMethod gMethods[] = { - NATIVE_METHOD(NativeBreakIterator, cloneImpl, "(J)J"), - NATIVE_METHOD(NativeBreakIterator, closeImpl, "(J)V"), - NATIVE_METHOD(NativeBreakIterator, currentImpl, "(JLjava/lang/String;)I"), - NATIVE_METHOD(NativeBreakIterator, firstImpl, "(JLjava/lang/String;)I"), - NATIVE_METHOD(NativeBreakIterator, followingImpl, "(JLjava/lang/String;I)I"), - NATIVE_METHOD(NativeBreakIterator, getCharacterInstanceImpl, "(Ljava/lang/String;)J"), - NATIVE_METHOD(NativeBreakIterator, getLineInstanceImpl, "(Ljava/lang/String;)J"), - NATIVE_METHOD(NativeBreakIterator, getSentenceInstanceImpl, "(Ljava/lang/String;)J"), - NATIVE_METHOD(NativeBreakIterator, getWordInstanceImpl, "(Ljava/lang/String;)J"), - NATIVE_METHOD(NativeBreakIterator, isBoundaryImpl, "(JLjava/lang/String;I)Z"), - NATIVE_METHOD(NativeBreakIterator, lastImpl, "(JLjava/lang/String;)I"), - NATIVE_METHOD(NativeBreakIterator, nextImpl, "(JLjava/lang/String;I)I"), - NATIVE_METHOD(NativeBreakIterator, precedingImpl, "(JLjava/lang/String;I)I"), - NATIVE_METHOD(NativeBreakIterator, previousImpl, "(JLjava/lang/String;)I"), - NATIVE_METHOD(NativeBreakIterator, setTextImpl, "(JLjava/lang/String;)V"), -}; -void register_libcore_icu_NativeBreakIterator(JNIEnv* env) { - jniRegisterNativeMethods(env, "libcore/icu/NativeBreakIterator", gMethods, NELEM(gMethods)); -} diff --git a/luni/src/main/native/libcore_icu_NativeCollation.cpp b/luni/src/main/native/libcore_icu_NativeCollation.cpp index 4ce42ec..f27d72e 100644 --- a/luni/src/main/native/libcore_icu_NativeCollation.cpp +++ b/luni/src/main/native/libcore_icu_NativeCollation.cpp @@ -15,10 +15,10 @@ #include "JniException.h" #include "ScopedStringChars.h" #include "ScopedUtfChars.h" -#include "UniquePtr.h" #include "unicode/ucol.h" #include "unicode/ucoleitr.h" #include <cutils/log.h> +#include <memory> // Manages a UCollationElements instance along with the jchar // array it is iterating over. The associated array can be unpinned @@ -124,7 +124,7 @@ static jlong NativeCollation_getCollationElementIterator(JNIEnv* env, jclass, jl return -1; } - UniquePtr<CollationElements> ce(new CollationElements); + std::unique_ptr<CollationElements> ce(new CollationElements); UErrorCode status = ce->start(env, javaSource, toCollator(address)); maybeThrowIcuException(env, "ucol_openElements", status); if (status == U_ZERO_ERROR) { @@ -156,7 +156,7 @@ static jbyteArray NativeCollation_getSortKey(JNIEnv* env, jclass, jlong address, const UCollator* collator = toCollator(address); // The buffer size prevents reallocation for most strings. uint8_t byteArray[128]; - UniquePtr<uint8_t[]> largerByteArray; + std::unique_ptr<uint8_t[]> largerByteArray; uint8_t* usedByteArray = byteArray; size_t byteArraySize = ucol_getSortKey(collator, source.get(), source.size(), usedByteArray, sizeof(byteArray) - 1); if (byteArraySize > sizeof(byteArray) - 1) { diff --git a/luni/src/main/native/libcore_icu_NativeConverter.cpp b/luni/src/main/native/libcore_icu_NativeConverter.cpp index 8dd439a..355cc78 100644 --- a/luni/src/main/native/libcore_icu_NativeConverter.cpp +++ b/luni/src/main/native/libcore_icu_NativeConverter.cpp @@ -23,7 +23,6 @@ #include "ScopedPrimitiveArray.h" #include "ScopedStringChars.h" #include "ScopedUtfChars.h" -#include "UniquePtr.h" #include "cutils/log.h" #include "toStringArray.h" #include "unicode/ucnv.h" @@ -32,6 +31,7 @@ #include "unicode/ustring.h" #include "unicode/utypes.h" +#include <memory> #include <vector> #include <stdlib.h> @@ -64,7 +64,7 @@ static UConverter* toUConverter(jlong address) { static bool collectStandardNames(JNIEnv* env, const char* canonicalName, const char* standard, std::vector<std::string>& result) { UErrorCode status = U_ZERO_ERROR; - UStringEnumeration e(ucnv_openStandardNames(canonicalName, standard, &status)); + icu::UStringEnumeration e(ucnv_openStandardNames(canonicalName, standard, &status)); if (maybeThrowIcuException(env, "ucnv_openStandardNames", status)) { return false; } @@ -75,7 +75,7 @@ static bool collectStandardNames(JNIEnv* env, const char* canonicalName, const c } for (int32_t i = 0; i < count; ++i) { - const UnicodeString* string = e.snext(status); + const icu::UnicodeString* string = e.snext(status); if (maybeThrowIcuException(env, "StringEnumeration::snext", status)) { return false; } @@ -104,7 +104,7 @@ static const char* getICUCanonicalName(const char* name) { } else if (strstr(name, "x-") == name) { // Check if the converter can be opened with the name given. error = U_ZERO_ERROR; - LocalUConverterPointer cnv(ucnv_open(name + 2, &error)); + icu::LocalUConverterPointer cnv(ucnv_open(name + 2, &error)); if (U_SUCCESS(error)) { return name + 2; } @@ -150,7 +150,7 @@ static jstring getJavaCanonicalName(JNIEnv* env, const char* icuCanonicalName) { if (name == NULL) { name = icuCanonicalName; } - UniquePtr<char[]> result(new char[2 + strlen(name) + 1]); + std::unique_ptr<char[]> result(new char[2 + strlen(name) + 1]); strcpy(&result[0], "x-"); strcat(&result[0], name); return env->NewStringUTF(&result[0]); @@ -537,12 +537,12 @@ static jboolean NativeConverter_contains(JNIEnv* env, jclass, jstring name1, jst } UErrorCode errorCode = U_ZERO_ERROR; - LocalUConverterPointer converter1(ucnv_open(name1Chars.c_str(), &errorCode)); - UnicodeSet set1; + icu::LocalUConverterPointer converter1(ucnv_open(name1Chars.c_str(), &errorCode)); + icu::UnicodeSet set1; ucnv_getUnicodeSet(&*converter1, set1.toUSet(), UCNV_ROUNDTRIP_SET, &errorCode); - LocalUConverterPointer converter2(ucnv_open(name2Chars.c_str(), &errorCode)); - UnicodeSet set2; + icu::LocalUConverterPointer converter2(ucnv_open(name2Chars.c_str(), &errorCode)); + icu::UnicodeSet set2; ucnv_getUnicodeSet(&*converter2, set2.toUSet(), UCNV_ROUNDTRIP_SET, &errorCode); return U_SUCCESS(errorCode) && set1.containsAll(set2); @@ -570,7 +570,7 @@ static jobject NativeConverter_charsetForName(JNIEnv* env, jclass, jstring chars { // ICU doesn't offer any "isSupported", so we just open and immediately close. UErrorCode error = U_ZERO_ERROR; - LocalUConverterPointer cnv(ucnv_open(icuCanonicalName, &error)); + icu::LocalUConverterPointer cnv(ucnv_open(icuCanonicalName, &error)); if (!U_SUCCESS(error)) { return NULL; } diff --git a/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp b/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp index 8e440e9..8c4a411 100644 --- a/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp +++ b/luni/src/main/native/libcore_icu_NativeDecimalFormat.cpp @@ -19,6 +19,7 @@ #include <stdlib.h> #include <string.h> +#include <memory> #include <vector> #include "cutils/log.h" @@ -36,18 +37,17 @@ #include "unicode/numfmt.h" #include "unicode/unum.h" #include "unicode/ustring.h" -#include "UniquePtr.h" #include "valueOf.h" -static DecimalFormat* toDecimalFormat(jlong addr) { - return reinterpret_cast<DecimalFormat*>(static_cast<uintptr_t>(addr)); +static icu::DecimalFormat* toDecimalFormat(jlong addr) { + return reinterpret_cast<icu::DecimalFormat*>(static_cast<uintptr_t>(addr)); } static UNumberFormat* toUNumberFormat(jlong addr) { return reinterpret_cast<UNumberFormat*>(static_cast<uintptr_t>(addr)); } -static DecimalFormatSymbols* makeDecimalFormatSymbols(JNIEnv* env, +static icu::DecimalFormatSymbols* makeDecimalFormatSymbols(JNIEnv* env, jstring currencySymbol0, jchar decimalSeparator, jchar digit, jstring exponentSeparator0, jchar groupingSeparator0, jstring infinity0, jstring internationalCurrencySymbol0, jstring minusSign0, @@ -60,36 +60,41 @@ static DecimalFormatSymbols* makeDecimalFormatSymbols(JNIEnv* env, ScopedJavaUnicodeString nan(env, nan0); ScopedJavaUnicodeString minusSign(env, minusSign0); ScopedJavaUnicodeString percent(env, percent0); - UnicodeString groupingSeparator(groupingSeparator0); - - DecimalFormatSymbols* result = new DecimalFormatSymbols; - result->setSymbol(DecimalFormatSymbols::kCurrencySymbol, currencySymbol.unicodeString()); - result->setSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol, UnicodeString(decimalSeparator)); - result->setSymbol(DecimalFormatSymbols::kDigitSymbol, UnicodeString(digit)); - result->setSymbol(DecimalFormatSymbols::kExponentialSymbol, exponentSeparator.unicodeString()); - result->setSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol, groupingSeparator); - result->setSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol, groupingSeparator); - result->setSymbol(DecimalFormatSymbols::kInfinitySymbol, infinity.unicodeString()); - result->setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, internationalCurrencySymbol.unicodeString()); - result->setSymbol(DecimalFormatSymbols::kMinusSignSymbol, minusSign.unicodeString()); - result->setSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol, UnicodeString(monetaryDecimalSeparator)); - result->setSymbol(DecimalFormatSymbols::kNaNSymbol, nan.unicodeString()); - result->setSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol, UnicodeString(patternSeparator)); - result->setSymbol(DecimalFormatSymbols::kPercentSymbol, percent.unicodeString()); - result->setSymbol(DecimalFormatSymbols::kPerMillSymbol, UnicodeString(perMill)); + icu::UnicodeString groupingSeparator(groupingSeparator0); + + UErrorCode status = U_ZERO_ERROR; + std::unique_ptr<icu::DecimalFormatSymbols> result(icu::DecimalFormatSymbols::createWithLastResortData(status)); + if (maybeThrowIcuException(env, "DecimalFormatSymbols::createWithLastResortData", status)) { + return NULL; + } + + result->setSymbol(icu::DecimalFormatSymbols::kCurrencySymbol, currencySymbol.unicodeString()); + result->setSymbol(icu::DecimalFormatSymbols::kDecimalSeparatorSymbol, icu::UnicodeString(decimalSeparator)); + result->setSymbol(icu::DecimalFormatSymbols::kDigitSymbol, icu::UnicodeString(digit)); + result->setSymbol(icu::DecimalFormatSymbols::kExponentialSymbol, exponentSeparator.unicodeString()); + result->setSymbol(icu::DecimalFormatSymbols::kGroupingSeparatorSymbol, groupingSeparator); + result->setSymbol(icu::DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol, groupingSeparator); + result->setSymbol(icu::DecimalFormatSymbols::kInfinitySymbol, infinity.unicodeString()); + result->setSymbol(icu::DecimalFormatSymbols::kIntlCurrencySymbol, internationalCurrencySymbol.unicodeString()); + result->setSymbol(icu::DecimalFormatSymbols::kMinusSignSymbol, minusSign.unicodeString()); + result->setSymbol(icu::DecimalFormatSymbols::kMonetarySeparatorSymbol, icu::UnicodeString(monetaryDecimalSeparator)); + result->setSymbol(icu::DecimalFormatSymbols::kNaNSymbol, nan.unicodeString()); + result->setSymbol(icu::DecimalFormatSymbols::kPatternSeparatorSymbol, icu::UnicodeString(patternSeparator)); + result->setSymbol(icu::DecimalFormatSymbols::kPercentSymbol, percent.unicodeString()); + result->setSymbol(icu::DecimalFormatSymbols::kPerMillSymbol, icu::UnicodeString(perMill)); // java.text.DecimalFormatSymbols just uses a zero digit, // but ICU >= 4.6 has a field for each decimal digit. - result->setSymbol(DecimalFormatSymbols::kZeroDigitSymbol, UnicodeString(zeroDigit + 0)); - result->setSymbol(DecimalFormatSymbols::kOneDigitSymbol, UnicodeString(zeroDigit + 1)); - result->setSymbol(DecimalFormatSymbols::kTwoDigitSymbol, UnicodeString(zeroDigit + 2)); - result->setSymbol(DecimalFormatSymbols::kThreeDigitSymbol, UnicodeString(zeroDigit + 3)); - result->setSymbol(DecimalFormatSymbols::kFourDigitSymbol, UnicodeString(zeroDigit + 4)); - result->setSymbol(DecimalFormatSymbols::kFiveDigitSymbol, UnicodeString(zeroDigit + 5)); - result->setSymbol(DecimalFormatSymbols::kSixDigitSymbol, UnicodeString(zeroDigit + 6)); - result->setSymbol(DecimalFormatSymbols::kSevenDigitSymbol, UnicodeString(zeroDigit + 7)); - result->setSymbol(DecimalFormatSymbols::kEightDigitSymbol, UnicodeString(zeroDigit + 8)); - result->setSymbol(DecimalFormatSymbols::kNineDigitSymbol, UnicodeString(zeroDigit + 9)); - return result; + result->setSymbol(icu::DecimalFormatSymbols::kZeroDigitSymbol, icu::UnicodeString(zeroDigit + 0)); + result->setSymbol(icu::DecimalFormatSymbols::kOneDigitSymbol, icu::UnicodeString(zeroDigit + 1)); + result->setSymbol(icu::DecimalFormatSymbols::kTwoDigitSymbol, icu::UnicodeString(zeroDigit + 2)); + result->setSymbol(icu::DecimalFormatSymbols::kThreeDigitSymbol, icu::UnicodeString(zeroDigit + 3)); + result->setSymbol(icu::DecimalFormatSymbols::kFourDigitSymbol, icu::UnicodeString(zeroDigit + 4)); + result->setSymbol(icu::DecimalFormatSymbols::kFiveDigitSymbol, icu::UnicodeString(zeroDigit + 5)); + result->setSymbol(icu::DecimalFormatSymbols::kSixDigitSymbol, icu::UnicodeString(zeroDigit + 6)); + result->setSymbol(icu::DecimalFormatSymbols::kSevenDigitSymbol, icu::UnicodeString(zeroDigit + 7)); + result->setSymbol(icu::DecimalFormatSymbols::kEightDigitSymbol, icu::UnicodeString(zeroDigit + 8)); + result->setSymbol(icu::DecimalFormatSymbols::kNineDigitSymbol, icu::UnicodeString(zeroDigit + 9)); + return result.release(); } static void NativeDecimalFormat_setDecimalFormatSymbols(JNIEnv* env, jclass, jlong addr, @@ -98,7 +103,7 @@ static void NativeDecimalFormat_setDecimalFormatSymbols(JNIEnv* env, jclass, jlo jstring internationalCurrencySymbol, jstring minusSign, jchar monetaryDecimalSeparator, jstring nan, jchar patternSeparator, jstring percent, jchar perMill, jchar zeroDigit) { - DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env, + icu::DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env, currencySymbol, decimalSeparator, digit, exponentSeparator, groupingSeparator, infinity, internationalCurrencySymbol, minusSign, monetaryDecimalSeparator, nan, patternSeparator, percent, perMill, @@ -118,12 +123,12 @@ static jlong NativeDecimalFormat_open(JNIEnv* env, jclass, jstring pattern0, if (!pattern.valid()) { return 0; } - DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env, + icu::DecimalFormatSymbols* symbols = makeDecimalFormatSymbols(env, currencySymbol, decimalSeparator, digit, exponentSeparator, groupingSeparator, infinity, internationalCurrencySymbol, minusSign, monetaryDecimalSeparator, nan, patternSeparator, percent, perMill, zeroDigit); - DecimalFormat* fmt = new DecimalFormat(pattern.unicodeString(), symbols, parseError, status); + icu::DecimalFormat* fmt = new icu::DecimalFormat(pattern.unicodeString(), symbols, parseError, status); if (fmt == NULL) { delete symbols; } @@ -136,8 +141,8 @@ static void NativeDecimalFormat_close(JNIEnv*, jclass, jlong addr) { } static void NativeDecimalFormat_setRoundingMode(JNIEnv*, jclass, jlong addr, jint mode, jdouble increment) { - DecimalFormat* fmt = toDecimalFormat(addr); - fmt->setRoundingMode(static_cast<DecimalFormat::ERoundingMode>(mode)); + icu::DecimalFormat* fmt = toDecimalFormat(addr); + fmt->setRoundingMode(static_cast<icu::DecimalFormat::ERoundingMode>(mode)); fmt->setRoundingIncrement(increment); } @@ -179,7 +184,7 @@ static jstring NativeDecimalFormat_getTextAttribute(JNIEnv* env, jclass, jlong a UNumberFormatTextAttribute attr = static_cast<UNumberFormatTextAttribute>(javaAttr); // Find out how long the result will be... - UniquePtr<UChar[]> chars; + std::unique_ptr<UChar[]> chars; uint32_t charCount = 0; uint32_t desiredCount = unum_getTextAttribute(fmt, attr, chars.get(), charCount, &status); if (status == U_BUFFER_OVERFLOW_ERROR) { @@ -197,7 +202,7 @@ static void NativeDecimalFormat_applyPatternImpl(JNIEnv* env, jclass, jlong addr if (!pattern.valid()) { return; } - DecimalFormat* fmt = toDecimalFormat(addr); + icu::DecimalFormat* fmt = toDecimalFormat(addr); UErrorCode status = U_ZERO_ERROR; const char* function; if (localized) { @@ -211,8 +216,8 @@ static void NativeDecimalFormat_applyPatternImpl(JNIEnv* env, jclass, jlong addr } static jstring NativeDecimalFormat_toPatternImpl(JNIEnv* env, jclass, jlong addr, jboolean localized) { - DecimalFormat* fmt = toDecimalFormat(addr); - UnicodeString pattern; + icu::DecimalFormat* fmt = toDecimalFormat(addr); + icu::UnicodeString pattern; if (localized) { fmt->toLocalizedPattern(pattern); } else { @@ -221,12 +226,12 @@ static jstring NativeDecimalFormat_toPatternImpl(JNIEnv* env, jclass, jlong addr return env->NewString(pattern.getBuffer(), pattern.length()); } -static jcharArray formatResult(JNIEnv* env, const UnicodeString& s, FieldPositionIterator* fpi, jobject javaFieldPositionIterator) { +static jcharArray formatResult(JNIEnv* env, const icu::UnicodeString& s, icu::FieldPositionIterator* fpi, jobject javaFieldPositionIterator) { static jmethodID gFPI_setData = env->GetMethodID(JniConstants::fieldPositionIteratorClass, "setData", "([I)V"); if (fpi != NULL) { std::vector<int32_t> data; - FieldPosition fp; + icu::FieldPosition fp; while (fpi->next(fp)) { data.push_back(fp.getField()); data.push_back(fp.getBeginIndex()); @@ -258,10 +263,10 @@ static jcharArray formatResult(JNIEnv* env, const UnicodeString& s, FieldPositio template <typename T> static jcharArray format(JNIEnv* env, jlong addr, jobject javaFieldPositionIterator, T value) { UErrorCode status = U_ZERO_ERROR; - UnicodeString s; - DecimalFormat* fmt = toDecimalFormat(addr); - FieldPositionIterator nativeFieldPositionIterator; - FieldPositionIterator* fpi = javaFieldPositionIterator ? &nativeFieldPositionIterator : NULL; + icu::UnicodeString s; + icu::DecimalFormat* fmt = toDecimalFormat(addr); + icu::FieldPositionIterator nativeFieldPositionIterator; + icu::FieldPositionIterator* fpi = javaFieldPositionIterator ? &nativeFieldPositionIterator : NULL; fmt->format(value, s, fpi, status); if (maybeThrowIcuException(env, "DecimalFormat::format", status)) { return NULL; @@ -282,7 +287,7 @@ static jcharArray NativeDecimalFormat_formatDigitList(JNIEnv* env, jclass, jlong if (chars.c_str() == NULL) { return NULL; } - StringPiece sp(chars.c_str()); + icu::StringPiece sp(chars.c_str()); return format(env, addr, javaFieldPositionIterator, sp); } @@ -293,7 +298,7 @@ static jobject newBigDecimal(JNIEnv* env, const char* value, jsize len) { // value is a UTF-8 string of invariant characters, but isn't guaranteed to be // null-terminated. NewStringUTF requires a terminated UTF-8 string. So we copy the // data to jchars using UnicodeString, and call NewString instead. - UnicodeString tmp(value, len, UnicodeString::kInvariant); + icu::UnicodeString tmp(value, len, icu::UnicodeString::kInvariant); jobject str = env->NewString(tmp.getBuffer(), tmp.length()); return env->NewObject(JniConstants::bigDecimalClass, gBigDecimal_init, str); } @@ -318,9 +323,9 @@ static jobject NativeDecimalFormat_parse(JNIEnv* env, jclass, jlong addr, jstrin return NULL; } - Formattable res; - ParsePosition pp(parsePos); - DecimalFormat* fmt = toDecimalFormat(addr); + icu::Formattable res; + icu::ParsePosition pp(parsePos); + icu::DecimalFormat* fmt = toDecimalFormat(addr); fmt->parse(src.unicodeString(), res, pp); if (pp.getErrorIndex() == -1) { @@ -332,7 +337,7 @@ static jobject NativeDecimalFormat_parse(JNIEnv* env, jclass, jlong addr, jstrin if (parseBigDecimal) { UErrorCode status = U_ZERO_ERROR; - StringPiece str = res.getDecimalNumber(status); + icu::StringPiece str = res.getDecimalNumber(status); if (U_SUCCESS(status)) { int len = str.length(); const char* data = str.data(); @@ -348,15 +353,15 @@ static jobject NativeDecimalFormat_parse(JNIEnv* env, jclass, jlong addr, jstrin } switch (res.getType()) { - case Formattable::kDouble: return doubleValueOf(env, res.getDouble()); - case Formattable::kLong: return longValueOf(env, res.getLong()); - case Formattable::kInt64: return longValueOf(env, res.getInt64()); + case icu::Formattable::kDouble: return doubleValueOf(env, res.getDouble()); + case icu::Formattable::kLong: return longValueOf(env, res.getLong()); + case icu::Formattable::kInt64: return longValueOf(env, res.getInt64()); default: return NULL; } } static jlong NativeDecimalFormat_cloneImpl(JNIEnv*, jclass, jlong addr) { - DecimalFormat* fmt = toDecimalFormat(addr); + icu::DecimalFormat* fmt = toDecimalFormat(addr); return reinterpret_cast<uintptr_t>(fmt->clone()); } diff --git a/luni/src/main/native/libcore_icu_NativeIDN.cpp b/luni/src/main/native/libcore_icu_NativeIDN.cpp index 16a6e1c..43f3ce5 100644 --- a/luni/src/main/native/libcore_icu_NativeIDN.cpp +++ b/luni/src/main/native/libcore_icu_NativeIDN.cpp @@ -39,9 +39,18 @@ static jstring NativeIDN_convertImpl(JNIEnv* env, jclass, jstring javaSrc, jint } UChar dst[256]; UErrorCode status = U_ZERO_ERROR; + + // We're stuck implementing IDNA-2003 for now since that's what we specify. + // + // TODO: Change our spec to IDNA-2008 + UTS-46 compatibility processing if + // it's safe enough. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" size_t resultLength = toAscii ? uidna_IDNToASCII(src.get(), src.size(), &dst[0], sizeof(dst), flags, NULL, &status) : uidna_IDNToUnicode(src.get(), src.size(), &dst[0], sizeof(dst), flags, NULL, &status); +#pragma GCC diagnostic pop + if (U_FAILURE(status)) { jniThrowException(env, "java/lang/IllegalArgumentException", u_errorName(status)); return NULL; diff --git a/luni/src/main/native/libcore_icu_NativeNormalizer.cpp b/luni/src/main/native/libcore_icu_NativeNormalizer.cpp index 8ae42d9..2d5e282 100644 --- a/luni/src/main/native/libcore_icu_NativeNormalizer.cpp +++ b/luni/src/main/native/libcore_icu_NativeNormalizer.cpp @@ -30,8 +30,8 @@ static jstring NativeNormalizer_normalizeImpl(JNIEnv* env, jclass, jstring s, ji } UNormalizationMode mode = static_cast<UNormalizationMode>(intMode); UErrorCode status = U_ZERO_ERROR; - UnicodeString dst; - Normalizer::normalize(src.unicodeString(), mode, 0, dst, status); + icu::UnicodeString dst; + icu::Normalizer::normalize(src.unicodeString(), mode, 0, dst, status); maybeThrowIcuException(env, "Normalizer::normalize", status); return dst.isBogus() ? NULL : env->NewString(dst.getBuffer(), dst.length()); } @@ -43,7 +43,7 @@ static jboolean NativeNormalizer_isNormalizedImpl(JNIEnv* env, jclass, jstring s } UNormalizationMode mode = static_cast<UNormalizationMode>(intMode); UErrorCode status = U_ZERO_ERROR; - UBool result = Normalizer::isNormalized(src.unicodeString(), mode, status); + UBool result = icu::Normalizer::isNormalized(src.unicodeString(), mode, status); maybeThrowIcuException(env, "Normalizer::isNormalized", status); return result; } diff --git a/luni/src/main/native/libcore_icu_NativePluralRules.cpp b/luni/src/main/native/libcore_icu_NativePluralRules.cpp index 8910a8c..f278485 100644 --- a/luni/src/main/native/libcore_icu_NativePluralRules.cpp +++ b/luni/src/main/native/libcore_icu_NativePluralRules.cpp @@ -25,8 +25,8 @@ #include <string> -static PluralRules* toPluralRules(jlong address) { - return reinterpret_cast<PluralRules*>(static_cast<uintptr_t>(address)); +static icu::PluralRules* toPluralRules(jlong address) { + return reinterpret_cast<icu::PluralRules*>(static_cast<uintptr_t>(address)); } static void NativePluralRules_finalizeImpl(JNIEnv*, jclass, jlong address) { @@ -48,15 +48,15 @@ static jlong NativePluralRules_forLocaleImpl(JNIEnv* env, jclass, jstring javaLo localeName[1] = 'i'; } - Locale locale = Locale::createFromName(localeName.c_str()); + icu::Locale locale = icu::Locale::createFromName(localeName.c_str()); UErrorCode status = U_ZERO_ERROR; - PluralRules* result = PluralRules::forLocale(locale, status); + icu::PluralRules* result = icu::PluralRules::forLocale(locale, status); maybeThrowIcuException(env, "PluralRules::forLocale", status); return reinterpret_cast<uintptr_t>(result); } static jint NativePluralRules_quantityForIntImpl(JNIEnv*, jclass, jlong address, jint value) { - UnicodeString keyword = toPluralRules(address)->select(value); + icu::UnicodeString keyword = toPluralRules(address)->select(value); if (keyword == "zero") { return 0; } else if (keyword == "one") { diff --git a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp index a7c9098..d30e7a3 100644 --- a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp +++ b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp @@ -16,6 +16,8 @@ #define LOG_TAG "TimeZoneNames" +#include <memory> + #include "IcuUtilities.h" #include "JNIHelp.h" #include "JniConstants.h" @@ -24,32 +26,31 @@ #include "ScopedJavaUnicodeString.h" #include "ScopedLocalRef.h" #include "ScopedUtfChars.h" -#include "UniquePtr.h" #include "unicode/calendar.h" #include "unicode/timezone.h" #include "unicode/tznames.h" -static bool isUtc(const UnicodeString& id) { - static const UnicodeString kEtcUct("Etc/UCT", 7, US_INV); - static const UnicodeString kEtcUtc("Etc/UTC", 7, US_INV); - static const UnicodeString kEtcUniversal("Etc/Universal", 13, US_INV); - static const UnicodeString kEtcZulu("Etc/Zulu", 8, US_INV); +static bool isUtc(const icu::UnicodeString& id) { + static const icu::UnicodeString kEtcUct("Etc/UCT", 7, US_INV); + static const icu::UnicodeString kEtcUtc("Etc/UTC", 7, US_INV); + static const icu::UnicodeString kEtcUniversal("Etc/Universal", 13, US_INV); + static const icu::UnicodeString kEtcZulu("Etc/Zulu", 8, US_INV); - static const UnicodeString kUct("UCT", 3, US_INV); - static const UnicodeString kUtc("UTC", 3, US_INV); - static const UnicodeString kUniversal("Universal", 9, US_INV); - static const UnicodeString kZulu("Zulu", 4, US_INV); + static const icu::UnicodeString kUct("UCT", 3, US_INV); + static const icu::UnicodeString kUtc("UTC", 3, US_INV); + static const icu::UnicodeString kUniversal("Universal", 9, US_INV); + static const icu::UnicodeString kZulu("Zulu", 4, US_INV); return id == kEtcUct || id == kEtcUtc || id == kEtcUniversal || id == kEtcZulu || id == kUct || id == kUtc || id == kUniversal || id == kZulu; } -static bool setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const UnicodeString& s) { +static bool setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const icu::UnicodeString& s) { // Fill in whatever we got. We don't use the display names if they're "GMT[+-]xx:xx" // because icu4c doesn't use the up-to-date time zone transition data, so it gets these // wrong. TimeZone.getDisplayName creates accurate names on demand. // TODO: investigate whether it's worth doing that work once in the Java wrapper instead of on-demand. - static const UnicodeString kGmt("GMT", 3, US_INV); + static const icu::UnicodeString kGmt("GMT", 3, US_INV); if (!s.isBogus() && !s.startsWith(kGmt)) { ScopedLocalRef<jstring> javaString(env, env->NewString(s.getBuffer(), s.length())); if (javaString.get() == NULL) { @@ -67,14 +68,14 @@ static void TimeZoneNames_fillZoneStrings(JNIEnv* env, jclass, jstring javaLocal } UErrorCode status = U_ZERO_ERROR; - UniquePtr<TimeZoneNames> names(TimeZoneNames::createInstance(icuLocale.locale(), status)); + std::unique_ptr<icu::TimeZoneNames> names(icu::TimeZoneNames::createInstance(icuLocale.locale(), status)); if (maybeThrowIcuException(env, "TimeZoneNames::createInstance", status)) { return; } - const UDate now(Calendar::getNow()); + const UDate now(icu::Calendar::getNow()); - static const UnicodeString kUtc("UTC", 3, US_INV); + static const icu::UnicodeString kUtc("UTC", 3, US_INV); size_t id_count = env->GetArrayLength(result); for (size_t i = 0; i < id_count; ++i) { @@ -87,13 +88,13 @@ static void TimeZoneNames_fillZoneStrings(JNIEnv* env, jclass, jstring javaLocal return; } - UnicodeString long_std; + icu::UnicodeString long_std; names->getDisplayName(zone_id.unicodeString(), UTZNM_LONG_STANDARD, now, long_std); - UnicodeString short_std; + icu::UnicodeString short_std; names->getDisplayName(zone_id.unicodeString(), UTZNM_SHORT_STANDARD, now, short_std); - UnicodeString long_dst; + icu::UnicodeString long_dst; names->getDisplayName(zone_id.unicodeString(), UTZNM_LONG_DAYLIGHT, now, long_dst); - UnicodeString short_dst; + icu::UnicodeString short_dst; names->getDisplayName(zone_id.unicodeString(), UTZNM_SHORT_DAYLIGHT, now, short_dst); if (isUtc(zone_id.unicodeString())) { @@ -123,7 +124,7 @@ static jstring TimeZoneNames_getExemplarLocation(JNIEnv* env, jclass, jstring ja } UErrorCode status = U_ZERO_ERROR; - UniquePtr<TimeZoneNames> names(TimeZoneNames::createInstance(icuLocale.locale(), status)); + std::unique_ptr<icu::TimeZoneNames> names(icu::TimeZoneNames::createInstance(icuLocale.locale(), status)); if (maybeThrowIcuException(env, "TimeZoneNames::createInstance", status)) { return NULL; } @@ -133,8 +134,8 @@ static jstring TimeZoneNames_getExemplarLocation(JNIEnv* env, jclass, jstring ja return NULL; } - UnicodeString s; - const UDate now(Calendar::getNow()); + icu::UnicodeString s; + const UDate now(icu::Calendar::getNow()); names->getDisplayName(tz.unicodeString(), UTZNM_EXEMPLAR_LOCATION, now, s); return env->NewString(s.getBuffer(), s.length()); } diff --git a/luni/src/main/native/libcore_icu_Transliterator.cpp b/luni/src/main/native/libcore_icu_Transliterator.cpp index 0c52053..ae21565 100644 --- a/luni/src/main/native/libcore_icu_Transliterator.cpp +++ b/luni/src/main/native/libcore_icu_Transliterator.cpp @@ -23,8 +23,8 @@ #include "ScopedJavaUnicodeString.h" #include "unicode/translit.h" -static Transliterator* fromPeer(jlong peer) { - return reinterpret_cast<Transliterator*>(static_cast<uintptr_t>(peer)); +static icu::Transliterator* fromPeer(jlong peer) { + return reinterpret_cast<icu::Transliterator*>(static_cast<uintptr_t>(peer)); } static jlong Transliterator_create(JNIEnv* env, jclass, jstring javaId) { @@ -33,7 +33,7 @@ static jlong Transliterator_create(JNIEnv* env, jclass, jstring javaId) { return 0; } UErrorCode status = U_ZERO_ERROR; - Transliterator* t = Transliterator::createInstance(id.unicodeString(), UTRANS_FORWARD, status); + icu::Transliterator* t = icu::Transliterator::createInstance(id.unicodeString(), UTRANS_FORWARD, status); if (maybeThrowIcuException(env, "Transliterator::createInstance", status)) { return 0; } @@ -46,18 +46,18 @@ static void Transliterator_destroy(JNIEnv*, jclass, jlong peer) { static jobjectArray Transliterator_getAvailableIDs(JNIEnv* env, jclass) { UErrorCode status = U_ZERO_ERROR; - StringEnumeration* e = Transliterator::getAvailableIDs(status); + icu::StringEnumeration* e = icu::Transliterator::getAvailableIDs(status); return fromStringEnumeration(env, status, "Transliterator::getAvailableIDs", e); } static jstring Transliterator_transliterate(JNIEnv* env, jclass, jlong peer, jstring javaString) { - Transliterator* t = fromPeer(peer); + icu::Transliterator* t = fromPeer(peer); ScopedJavaUnicodeString string(env, javaString); if (!string.valid()) { return NULL; } - UnicodeString& s(string.unicodeString()); + icu::UnicodeString& s(string.unicodeString()); t->transliterate(s); return env->NewString(s.getBuffer(), s.length()); } diff --git a/luni/src/main/native/libcore_io_Memory.cpp b/luni/src/main/native/libcore_io_Memory.cpp index 9edbfb8..5122a6c 100644 --- a/luni/src/main/native/libcore_io_Memory.cpp +++ b/luni/src/main/native/libcore_io_Memory.cpp @@ -21,32 +21,12 @@ #include "Portability.h" #include "ScopedBytes.h" #include "ScopedPrimitiveArray.h" -#include "UniquePtr.h" #include <errno.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> -#if defined(__arm__) -// 32-bit ARM has load/store alignment restrictions for longs. -#define LONG_ALIGNMENT_MASK 0x3 -#define INT_ALIGNMENT_MASK 0x0 -#define SHORT_ALIGNMENT_MASK 0x0 -#elif defined(__mips__) -// MIPS has load/store alignment restrictions for longs, ints and shorts. -#define LONG_ALIGNMENT_MASK 0x7 -#define INT_ALIGNMENT_MASK 0x3 -#define SHORT_ALIGNMENT_MASK 0x1 -#elif defined(__aarch64__) || defined(__i386__) || defined(__x86_64__) -// These architectures can load anything at any alignment. -#define LONG_ALIGNMENT_MASK 0x0 -#define INT_ALIGNMENT_MASK 0x0 -#define SHORT_ALIGNMENT_MASK 0x0 -#else -#error unknown load/store alignment restrictions for this architecture -#endif - // Use packed structures for access to unaligned data on targets with alignment restrictions. // The compiler will generate appropriate code to access these structures without // generating alignment exceptions. @@ -82,63 +62,31 @@ static inline void swapShorts(jshort* dstShorts, const jshort* srcShorts, size_t // Do 32-bit swaps as long as possible... jint* dst = reinterpret_cast<jint*>(dstShorts); const jint* src = reinterpret_cast<const jint*>(srcShorts); - - if ((reinterpret_cast<uintptr_t>(dst) & INT_ALIGNMENT_MASK) == 0 && - (reinterpret_cast<uintptr_t>(src) & INT_ALIGNMENT_MASK) == 0) { - for (size_t i = 0; i < count / 2; ++i) { - jint v = *src++; - *dst++ = bswap_2x16(v); - } - // ...with one last 16-bit swap if necessary. - if ((count % 2) != 0) { - jshort v = *reinterpret_cast<const jshort*>(src); - *reinterpret_cast<jshort*>(dst) = bswap_16(v); - } - } else { - for (size_t i = 0; i < count / 2; ++i) { - jint v = get_unaligned<jint>(src++); - put_unaligned<jint>(dst++, bswap_2x16(v)); - } - if ((count % 2) != 0) { - jshort v = get_unaligned<jshort>(reinterpret_cast<const jshort*>(src)); - put_unaligned<jshort>(reinterpret_cast<jshort*>(dst), bswap_16(v)); - } + for (size_t i = 0; i < count / 2; ++i) { + jint v = get_unaligned<jint>(src++); + put_unaligned<jint>(dst++, bswap_2x16(v)); + } + if ((count % 2) != 0) { + jshort v = get_unaligned<jshort>(reinterpret_cast<const jshort*>(src)); + put_unaligned<jshort>(reinterpret_cast<jshort*>(dst), bswap_16(v)); } } static inline void swapInts(jint* dstInts, const jint* srcInts, size_t count) { - if ((reinterpret_cast<uintptr_t>(dstInts) & INT_ALIGNMENT_MASK) == 0 && - (reinterpret_cast<uintptr_t>(srcInts) & INT_ALIGNMENT_MASK) == 0) { - for (size_t i = 0; i < count; ++i) { - jint v = *srcInts++; - *dstInts++ = bswap_32(v); - } - } else { - for (size_t i = 0; i < count; ++i) { - jint v = get_unaligned<int>(srcInts++); - put_unaligned<jint>(dstInts++, bswap_32(v)); - } + for (size_t i = 0; i < count; ++i) { + jint v = get_unaligned<int>(srcInts++); + put_unaligned<jint>(dstInts++, bswap_32(v)); } } static inline void swapLongs(jlong* dstLongs, const jlong* srcLongs, size_t count) { jint* dst = reinterpret_cast<jint*>(dstLongs); const jint* src = reinterpret_cast<const jint*>(srcLongs); - if ((reinterpret_cast<uintptr_t>(dstLongs) & INT_ALIGNMENT_MASK) == 0 && - (reinterpret_cast<uintptr_t>(srcLongs) & INT_ALIGNMENT_MASK) == 0) { - for (size_t i = 0; i < count; ++i) { - jint v1 = *src++; - jint v2 = *src++; - *dst++ = bswap_32(v2); - *dst++ = bswap_32(v1); - } - } else { - for (size_t i = 0; i < count; ++i) { - jint v1 = get_unaligned<jint>(src++); - jint v2 = get_unaligned<jint>(src++); - put_unaligned<jint>(dst++, bswap_32(v2)); - put_unaligned<jint>(dst++, bswap_32(v1)); - } + for (size_t i = 0; i < count; ++i) { + jint v1 = get_unaligned<jint>(src++); + jint v2 = get_unaligned<jint>(src++); + put_unaligned<jint>(dst++, bswap_32(v2)); + put_unaligned<jint>(dst++, bswap_32(v1)); } } @@ -260,39 +208,27 @@ static void Memory_pokeShortArray(JNIEnv* env, jclass, jlong dstAddress, jshortA } static jshort Memory_peekShortNative(JNIEnv*, jclass, jlong srcAddress) { - return *cast<const jshort*>(srcAddress); + return get_unaligned<jshort>(cast<const jshort*>(srcAddress)); } static void Memory_pokeShortNative(JNIEnv*, jclass, jlong dstAddress, jshort value) { - *cast<jshort*>(dstAddress) = value; + put_unaligned<jshort>(cast<jshort*>(dstAddress), value); } static jint Memory_peekIntNative(JNIEnv*, jclass, jlong srcAddress) { - return *cast<const jint*>(srcAddress); + return get_unaligned<jint>(cast<const jint*>(srcAddress)); } static void Memory_pokeIntNative(JNIEnv*, jclass, jlong dstAddress, jint value) { - *cast<jint*>(dstAddress) = value; + put_unaligned<jint>(cast<jint*>(dstAddress), value); } static jlong Memory_peekLongNative(JNIEnv*, jclass, jlong srcAddress) { - jlong result; - const jlong* src = cast<const jlong*>(srcAddress); - if ((srcAddress & LONG_ALIGNMENT_MASK) == 0) { - result = *src; - } else { - result = get_unaligned<jlong>(src); - } - return result; + return get_unaligned<jlong>(cast<const jlong*>(srcAddress)); } static void Memory_pokeLongNative(JNIEnv*, jclass, jlong dstAddress, jlong value) { - jlong* dst = cast<jlong*>(dstAddress); - if ((dstAddress & LONG_ALIGNMENT_MASK) == 0) { - *dst = value; - } else { - put_unaligned<jlong>(dst, value); - } + put_unaligned<jlong>(cast<jlong*>(dstAddress), value); } static void unsafeBulkCopy(jbyte* dst, const jbyte* src, jint byteCount, diff --git a/luni/src/main/native/libcore_io_Posix.cpp b/luni/src/main/native/libcore_io_Posix.cpp index 7e9b22e..f6af483 100644 --- a/luni/src/main/native/libcore_io_Posix.cpp +++ b/luni/src/main/native/libcore_io_Posix.cpp @@ -25,20 +25,20 @@ #include "NetworkUtilities.h" #include "Portability.h" #include "readlink.h" -#include "../../bionic/libc/dns/include/resolv_netid.h" // For android_getaddrinfofornet. #include "ScopedBytes.h" #include "ScopedLocalRef.h" #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" #include "toStringArray.h" -#include "UniquePtr.h" #include <arpa/inet.h> #include <errno.h> #include <fcntl.h> +#include <linux/rtnetlink.h> #include <net/if.h> #include <netdb.h> #include <netinet/in.h> +#include <netpacket/packet.h> #include <poll.h> #include <pwd.h> #include <signal.h> @@ -61,7 +61,7 @@ #include <sys/wait.h> #include <termios.h> #include <unistd.h> - +#include <memory> #ifndef __unused #define __unused __attribute__((__unused__)) @@ -79,6 +79,52 @@ struct addrinfo_deleter { } }; +static bool isIPv4MappedAddress(const sockaddr *sa) { + const sockaddr_in6 *sin6 = reinterpret_cast<const sockaddr_in6*>(sa); + return sa != NULL && sa->sa_family == AF_INET6 && + (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) || + IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)); // We map 0.0.0.0 to ::, so :: is mapped. +} + +/** + * Perform a socket operation that specifies an IP address, possibly falling back from specifying + * the address as an IPv4-mapped IPv6 address in a struct sockaddr_in6 to specifying it as an IPv4 + * address in a struct sockaddr_in. + * + * This is needed because all sockets created by the java.net APIs are IPv6 sockets, and on those + * sockets, IPv4 operations use IPv4-mapped addresses stored in a struct sockaddr_in6. But sockets + * created using Posix.socket(AF_INET, ...) are IPv4 sockets and only support operations using IPv4 + * socket addresses structures. + */ +#define NET_IPV4_FALLBACK(jni_env, return_type, syscall_name, java_fd, java_addr, port, null_addr_ok, args...) ({ \ + return_type _rc = -1; \ + do { \ + sockaddr_storage _ss; \ + socklen_t _salen; \ + if (java_addr == NULL && null_addr_ok) { \ + /* No IP address specified (e.g., sendto() on a connected socket). */ \ + _salen = 0; \ + } else if (!inetAddressToSockaddr(jni_env, java_addr, port, _ss, _salen)) { \ + /* Invalid socket address, return -1. inetAddressToSockaddr has already thrown. */ \ + break; \ + } \ + sockaddr* _sa = _salen ? reinterpret_cast<sockaddr*>(&_ss) : NULL; \ + /* inetAddressToSockaddr always returns an IPv6 sockaddr. Assume that java_fd was created \ + * by Java API calls, which always create IPv6 socket fds, and pass it in as is. */ \ + _rc = NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ##args, _sa, _salen); \ + if (_rc == -1 && errno == EAFNOSUPPORT && _salen && isIPv4MappedAddress(_sa)) { \ + /* We passed in an IPv4 address in an IPv6 sockaddr and the kernel told us that we got \ + * the address family wrong. Pass in the same address in an IPv4 sockaddr. */ \ + jni_env->ExceptionClear(); \ + if (!inetAddressToSockaddrVerbatim(jni_env, java_addr, port, _ss, _salen)) { \ + break; \ + } \ + _sa = reinterpret_cast<sockaddr*>(&_ss); \ + _rc = NET_FAILURE_RETRY(jni_env, return_type, syscall_name, java_fd, ##args, _sa, _salen); \ + } \ + } while (0); \ + _rc; }) \ + /** * Used to retry networking system calls that can be interrupted with a signal. Unlike * TEMP_FAILURE_RETRY, this also handles the case where @@ -150,6 +196,9 @@ struct addrinfo_deleter { } while (_rc == -1); /* && _syscallErrno == EINTR && !_wasSignaled */ \ _rc; }) +#define NULL_ADDR_OK true +#define NULL_ADDR_FORBIDDEN false + static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor3, jmethodID ctor2, const char* functionName, int error) { jthrowable cause = NULL; @@ -268,14 +317,43 @@ private: }; static jobject makeSocketAddress(JNIEnv* env, const sockaddr_storage& ss) { - jint port; - jobject inetAddress = sockaddrToInetAddress(env, ss, &port); - if (inetAddress == NULL) { - return NULL; - } - static jmethodID ctor = env->GetMethodID(JniConstants::inetSocketAddressClass, "<init>", - "(Ljava/net/InetAddress;I)V"); - return env->NewObject(JniConstants::inetSocketAddressClass, ctor, inetAddress, port); + if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6 || ss.ss_family == AF_UNIX) { + jint port; + jobject inetAddress = sockaddrToInetAddress(env, ss, &port); + if (inetAddress == NULL) { + return NULL; // Exception already thrown. + } + static jmethodID ctor = env->GetMethodID(JniConstants::inetSocketAddressClass, + "<init>", "(Ljava/net/InetAddress;I)V"); + return env->NewObject(JniConstants::inetSocketAddressClass, ctor, inetAddress, port); + } else if (ss.ss_family == AF_NETLINK) { + const struct sockaddr_nl* nl_addr = reinterpret_cast<const struct sockaddr_nl*>(&ss); + static jmethodID ctor = env->GetMethodID(JniConstants::netlinkSocketAddressClass, + "<init>", "(II)V"); + return env->NewObject(JniConstants::netlinkSocketAddressClass, ctor, + static_cast<jint>(nl_addr->nl_pid), + static_cast<jint>(nl_addr->nl_groups)); + } else if (ss.ss_family == AF_PACKET) { + const struct sockaddr_ll* sll = reinterpret_cast<const struct sockaddr_ll*>(&ss); + static jmethodID ctor = env->GetMethodID(JniConstants::packetSocketAddressClass, + "<init>", "(SISB[B)V"); + ScopedLocalRef<jbyteArray> byteArray(env, env->NewByteArray(sll->sll_halen)); + if (byteArray.get() == NULL) { + return NULL; + } + env->SetByteArrayRegion(byteArray.get(), 0, sll->sll_halen, + reinterpret_cast<const jbyte*>(sll->sll_addr)); + jobject packetSocketAddress = env->NewObject(JniConstants::packetSocketAddressClass, ctor, + static_cast<jshort>(ntohs(sll->sll_protocol)), + static_cast<jint>(sll->sll_ifindex), + static_cast<jshort>(sll->sll_hatype), + static_cast<jbyte>(sll->sll_pkttype), + byteArray.get()); + return packetSocketAddress; + } + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "unsupported ss_family: %d", + ss.ss_family); + return NULL; } static jobject makeStructPasswd(JNIEnv* env, const struct passwd& pw) { @@ -386,6 +464,93 @@ static bool fillInetSocketAddress(JNIEnv* env, jint rc, jobject javaInetSocketAd return true; } +static void javaInetSocketAddressToInetAddressAndPort( + JNIEnv* env, jobject javaInetSocketAddress, jobject& javaInetAddress, jint& port) { + static jfieldID addressFid = env->GetFieldID( + JniConstants::inetSocketAddressClass, "addr", "Ljava/net/InetAddress;"); + static jfieldID portFid = env->GetFieldID(JniConstants::inetSocketAddressClass, "port", "I"); + javaInetAddress = env->GetObjectField(javaInetSocketAddress, addressFid); + port = env->GetIntField(javaInetSocketAddress, portFid); +} + +static bool javaInetSocketAddressToSockaddr( + JNIEnv* env, jobject javaSocketAddress, sockaddr_storage& ss, socklen_t& sa_len) { + jobject javaInetAddress; + jint port; + javaInetSocketAddressToInetAddressAndPort(env, javaSocketAddress, javaInetAddress, port); + return inetAddressToSockaddr(env, javaInetAddress, port, ss, sa_len); +} + +static bool javaNetlinkSocketAddressToSockaddr( + JNIEnv* env, jobject javaSocketAddress, sockaddr_storage& ss, socklen_t& sa_len) { + static jfieldID nlPidFid = env->GetFieldID( + JniConstants::netlinkSocketAddressClass, "nlPortId", "I"); + static jfieldID nlGroupsFid = env->GetFieldID( + JniConstants::netlinkSocketAddressClass, "nlGroupsMask", "I"); + + sockaddr_nl *nlAddr = reinterpret_cast<sockaddr_nl *>(&ss); + nlAddr->nl_family = AF_NETLINK; + nlAddr->nl_pid = env->GetIntField(javaSocketAddress, nlPidFid); + nlAddr->nl_groups = env->GetIntField(javaSocketAddress, nlGroupsFid); + sa_len = sizeof(sockaddr_nl); + return true; +} + +static bool javaPacketSocketAddressToSockaddr( + JNIEnv* env, jobject javaSocketAddress, sockaddr_storage& ss, socklen_t& sa_len) { + static jfieldID protocolFid = env->GetFieldID( + JniConstants::packetSocketAddressClass, "sll_protocol", "S"); + static jfieldID ifindexFid = env->GetFieldID( + JniConstants::packetSocketAddressClass, "sll_ifindex", "I"); + static jfieldID hatypeFid = env->GetFieldID( + JniConstants::packetSocketAddressClass, "sll_hatype", "S"); + static jfieldID pkttypeFid = env->GetFieldID( + JniConstants::packetSocketAddressClass, "sll_pkttype", "B"); + static jfieldID addrFid = env->GetFieldID( + JniConstants::packetSocketAddressClass, "sll_addr", "[B"); + + sockaddr_ll *sll = reinterpret_cast<sockaddr_ll *>(&ss); + sll->sll_family = AF_PACKET; + sll->sll_protocol = htons(env->GetShortField(javaSocketAddress, protocolFid)); + sll->sll_ifindex = env->GetIntField(javaSocketAddress, ifindexFid); + sll->sll_hatype = env->GetShortField(javaSocketAddress, hatypeFid); + sll->sll_pkttype = env->GetByteField(javaSocketAddress, pkttypeFid); + + jbyteArray sllAddr = (jbyteArray) env->GetObjectField(javaSocketAddress, addrFid); + if (sllAddr == NULL) { + sll->sll_halen = 0; + memset(&sll->sll_addr, 0, sizeof(sll->sll_addr)); + } else { + jsize len = env->GetArrayLength(sllAddr); + if ((size_t) len > sizeof(sll->sll_addr)) { + len = sizeof(sll->sll_addr); + } + sll->sll_halen = len; + env->GetByteArrayRegion(sllAddr, 0, len, (jbyte*) sll->sll_addr); + } + sa_len = sizeof(sockaddr_ll); + return true; +} + +static bool javaSocketAddressToSockaddr( + JNIEnv* env, jobject javaSocketAddress, sockaddr_storage& ss, socklen_t& sa_len) { + if (javaSocketAddress == NULL) { + jniThrowNullPointerException(env, NULL); + return false; + } + + if (env->IsInstanceOf(javaSocketAddress, JniConstants::netlinkSocketAddressClass)) { + return javaNetlinkSocketAddressToSockaddr(env, javaSocketAddress, ss, sa_len); + } else if (env->IsInstanceOf(javaSocketAddress, JniConstants::inetSocketAddressClass)) { + return javaInetSocketAddressToSockaddr(env, javaSocketAddress, ss, sa_len); + } else if (env->IsInstanceOf(javaSocketAddress, JniConstants::packetSocketAddressClass)) { + return javaPacketSocketAddressToSockaddr(env, javaSocketAddress, ss, sa_len); + } + jniThrowException(env, "java/lang/UnsupportedOperationException", + "unsupported SocketAddress subclass"); + return false; +} + static jobject doStat(JNIEnv* env, jstring javaPath, bool isLstat) { ScopedUtfChars path(env, javaPath); if (path.c_str() == NULL) { @@ -446,7 +611,7 @@ private: } JNIEnv* mEnv; - UniquePtr<char[]> mBuffer; + std::unique_ptr<char[]> mBuffer; size_t mBufferSize; struct passwd mPwd; struct passwd* mResult; @@ -479,11 +644,18 @@ static jboolean Posix_access(JNIEnv* env, jobject, jstring javaPath, jint mode) } static void Posix_bind(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) { + // We don't need the return value because we'll already have thrown. + (void) NET_IPV4_FALLBACK(env, int, bind, javaFd, javaAddress, port, NULL_ADDR_FORBIDDEN); +} + +static void Posix_bindSocketAddress( + JNIEnv* env, jobject, jobject javaFd, jobject javaSocketAddress) { sockaddr_storage ss; socklen_t sa_len; - if (!inetAddressToSockaddr(env, javaAddress, port, ss, sa_len)) { - return; + if (!javaSocketAddressToSockaddr(env, javaSocketAddress, ss, sa_len)) { + return; // Exception already thrown. } + const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss); // We don't need the return value because we'll already have thrown. (void) NET_FAILURE_RETRY(env, int, bind, javaFd, sa, sa_len); @@ -518,11 +690,17 @@ static void Posix_close(JNIEnv* env, jobject, jobject javaFd) { } static void Posix_connect(JNIEnv* env, jobject, jobject javaFd, jobject javaAddress, jint port) { + (void) NET_IPV4_FALLBACK(env, int, connect, javaFd, javaAddress, port, NULL_ADDR_FORBIDDEN); +} + +static void Posix_connectSocketAddress( + JNIEnv* env, jobject, jobject javaFd, jobject javaSocketAddress) { sockaddr_storage ss; socklen_t sa_len; - if (!inetAddressToSockaddr(env, javaAddress, port, ss, sa_len)) { - return; + if (!javaSocketAddressToSockaddr(env, javaSocketAddress, ss, sa_len)) { + return; // Exception already thrown. } + const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss); // We don't need the return value because we'll already have thrown. (void) NET_FAILURE_RETRY(env, int, connect, javaFd, sa, sa_len); @@ -553,7 +731,7 @@ static void Posix_execve(JNIEnv* env, jobject, jstring javaFilename, jobjectArra ExecStrings argv(env, javaArgv); ExecStrings envp(env, javaEnvp); - execve(path.c_str(), argv.get(), envp.get()); + TEMP_FAILURE_RETRY(execve(path.c_str(), argv.get(), envp.get())); throwErrnoException(env, "execve"); } @@ -565,7 +743,7 @@ static void Posix_execv(JNIEnv* env, jobject, jstring javaFilename, jobjectArray } ExecStrings argv(env, javaArgv); - execv(path.c_str(), argv.get()); + TEMP_FAILURE_RETRY(execv(path.c_str(), argv.get())); throwErrnoException(env, "execv"); } @@ -580,16 +758,6 @@ static void Posix_fchown(JNIEnv* env, jobject, jobject javaFd, jint uid, jint gi throwIfMinusOne(env, "fchown", TEMP_FAILURE_RETRY(fchown(fd, uid, gid))); } -static jint Posix_fcntlVoid(JNIEnv* env, jobject, jobject javaFd, jint cmd) { - int fd = jniGetFDFromFileDescriptor(env, javaFd); - return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, cmd))); -} - -static jint Posix_fcntlLong(JNIEnv* env, jobject, jobject javaFd, jint cmd, jlong arg) { - int fd = jniGetFDFromFileDescriptor(env, javaFd); - return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, cmd, arg))); -} - static jint Posix_fcntlFlock(JNIEnv* env, jobject, jobject javaFd, jint cmd, jobject javaFlock) { static jfieldID typeFid = env->GetFieldID(JniConstants::structFlockClass, "l_type", "S"); static jfieldID whenceFid = env->GetFieldID(JniConstants::structFlockClass, "l_whence", "S"); @@ -616,6 +784,16 @@ static jint Posix_fcntlFlock(JNIEnv* env, jobject, jobject javaFd, jint cmd, job return rc; } +static jint Posix_fcntlInt(JNIEnv* env, jobject, jobject javaFd, jint cmd, jint arg) { + int fd = jniGetFDFromFileDescriptor(env, javaFd); + return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, cmd, arg))); +} + +static jint Posix_fcntlVoid(JNIEnv* env, jobject, jobject javaFd, jint cmd) { + int fd = jniGetFDFromFileDescriptor(env, javaFd); + return throwIfMinusOne(env, "fcntl", TEMP_FAILURE_RETRY(fcntl(fd, cmd))); +} + static void Posix_fdatasync(JNIEnv* env, jobject, jobject javaFd) { int fd = jniGetFDFromFileDescriptor(env, javaFd); throwIfMinusOne(env, "fdatasync", TEMP_FAILURE_RETRY(fdatasync(fd))); @@ -679,7 +857,7 @@ static jobjectArray Posix_android_getaddrinfo(JNIEnv* env, jobject, jstring java addrinfo* addressList = NULL; errno = 0; int rc = android_getaddrinfofornet(node.c_str(), NULL, &hints, netId, 0, &addressList); - UniquePtr<addrinfo, addrinfo_deleter> addressListDeleter(addressList); + std::unique_ptr<addrinfo, addrinfo_deleter> addressListDeleter(addressList); if (rc != 0) { throwGaiException(env, "android_getaddrinfo", rc); return NULL; @@ -765,12 +943,16 @@ static jobject Posix_getpeername(JNIEnv* env, jobject, jobject javaFd) { return doGetSockName(env, javaFd, false); } +static jint Posix_getpgid(JNIEnv* env, jobject, jint pid) { + return throwIfMinusOne(env, "getpgid", TEMP_FAILURE_RETRY(getpgid(pid))); +} + static jint Posix_getpid(JNIEnv*, jobject) { - return getpid(); + return TEMP_FAILURE_RETRY(getpid()); } static jint Posix_getppid(JNIEnv*, jobject) { - return getppid(); + return TEMP_FAILURE_RETRY(getppid()); } static jobject Posix_getpwnam(JNIEnv* env, jobject, jstring javaName) { @@ -868,8 +1050,9 @@ static jint Posix_gettid(JNIEnv* env __unused, jobject) { return 0; } return static_cast<jint>(owner); +#elif defined(__BIONIC__) + return TEMP_FAILURE_RETRY(gettid()); #else - // Neither bionic nor glibc exposes gettid(2). return syscall(__NR_gettid); #endif } @@ -1036,9 +1219,13 @@ static jobject Posix_open(JNIEnv* env, jobject, jstring javaPath, jint flags, ji return fd != -1 ? jniCreateFileDescriptor(env, fd) : NULL; } -static jobjectArray Posix_pipe(JNIEnv* env, jobject) { +static jobjectArray Posix_pipe2(JNIEnv* env, jobject, jint flags __unused) { +#ifdef __APPLE__ + jniThrowException(env, "java/lang/UnsupportedOperationException", "no pipe2 on Mac OS"); + return NULL; +#else int fds[2]; - throwIfMinusOne(env, "pipe", TEMP_FAILURE_RETRY(pipe(&fds[0]))); + throwIfMinusOne(env, "pipe2", TEMP_FAILURE_RETRY(pipe2(&fds[0], flags))); jobjectArray result = env->NewObjectArray(2, JniConstants::fileDescriptorClass, NULL); if (result == NULL) { return NULL; @@ -1054,6 +1241,7 @@ static jobjectArray Posix_pipe(JNIEnv* env, jobject) { } } return result; +#endif } static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, jint timeoutMs) { @@ -1063,7 +1251,7 @@ static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, jint time // Turn the Java android.system.StructPollfd[] into a C++ struct pollfd[]. size_t arrayLength = env->GetArrayLength(javaStructs); - UniquePtr<struct pollfd[]> fds(new struct pollfd[arrayLength]); + std::unique_ptr<struct pollfd[]> fds(new struct pollfd[arrayLength]); memset(fds.get(), 0, sizeof(struct pollfd) * arrayLength); size_t count = 0; // Some trailing array elements may be irrelevant. (See below.) for (size_t i = 0; i < arrayLength; ++i) { @@ -1084,7 +1272,40 @@ static jint Posix_poll(JNIEnv* env, jobject, jobjectArray javaStructs, jint time for (size_t i = 0; i < count; ++i) { monitors.push_back(new AsynchronousCloseMonitor(fds[i].fd)); } - int rc = poll(fds.get(), count, timeoutMs); + + int rc; + while (true) { + timespec before; + clock_gettime(CLOCK_MONOTONIC, &before); + + rc = poll(fds.get(), count, timeoutMs); + if (rc >= 0 || errno != EINTR) { + break; + } + + // We got EINTR. Work out how much of the original timeout is still left. + if (timeoutMs > 0) { + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + timespec diff; + diff.tv_sec = now.tv_sec - before.tv_sec; + diff.tv_nsec = now.tv_nsec - before.tv_nsec; + if (diff.tv_nsec < 0) { + --diff.tv_sec; + diff.tv_nsec += 1000000000; + } + + jint diffMs = diff.tv_sec * 1000 + diff.tv_nsec / 1000000; + if (diffMs >= timeoutMs) { + rc = 0; // We have less than 1ms left anyway, so just time out. + break; + } + + timeoutMs -= diffMs; + } + } + for (size_t i = 0; i < monitors.size(); ++i) { delete monitors[i]; } @@ -1111,7 +1332,8 @@ static void Posix_posix_fallocate(JNIEnv* env, jobject, jobject javaFd __unused, "fallocate doesn't exist on a Mac"); #else int fd = jniGetFDFromFileDescriptor(env, javaFd); - errno = TEMP_FAILURE_RETRY(posix_fallocate64(fd, offset, length)); + while ((errno = posix_fallocate64(fd, offset, length)) == EINTR) { + } if (errno != 0) { throwErrnoException(env, "posix_fallocate"); } @@ -1124,9 +1346,11 @@ static jint Posix_prctl(JNIEnv* env, jobject, jint option __unused, jlong arg2 _ jniThrowException(env, "java/lang/UnsupportedOperationException", "prctl doesn't exist on a Mac"); return 0; #else - int result = prctl(static_cast<int>(option), - static_cast<unsigned long>(arg2), static_cast<unsigned long>(arg3), - static_cast<unsigned long>(arg4), static_cast<unsigned long>(arg5)); + int result = TEMP_FAILURE_RETRY(prctl(static_cast<int>(option), + static_cast<unsigned long>(arg2), + static_cast<unsigned long>(arg3), + static_cast<unsigned long>(arg4), + static_cast<unsigned long>(arg5))); return throwIfMinusOne(env, "prctl", result); #endif } @@ -1235,13 +1459,35 @@ static jint Posix_sendtoBytes(JNIEnv* env, jobject, jobject javaFd, jobject java if (bytes.get() == NULL) { return -1; } + + return NET_IPV4_FALLBACK(env, ssize_t, sendto, javaFd, javaInetAddress, port, + NULL_ADDR_OK, bytes.get() + byteOffset, byteCount, flags); +} + +static jint Posix_sendtoBytesSocketAddress(JNIEnv* env, jobject, jobject javaFd, jobject javaBytes, jint byteOffset, jint byteCount, jint flags, jobject javaSocketAddress) { + if (env->IsInstanceOf(javaSocketAddress, JniConstants::inetSocketAddressClass)) { + // Use the InetAddress version so we get the benefit of NET_IPV4_FALLBACK. + jobject javaInetAddress; + jint port; + javaInetSocketAddressToInetAddressAndPort(env, javaSocketAddress, javaInetAddress, port); + return Posix_sendtoBytes(env, NULL, javaFd, javaBytes, byteOffset, byteCount, flags, + javaInetAddress, port); + } + + ScopedBytesRO bytes(env, javaBytes); + if (bytes.get() == NULL) { + return -1; + } + sockaddr_storage ss; - socklen_t sa_len = 0; - if (javaInetAddress != NULL && !inetAddressToSockaddr(env, javaInetAddress, port, ss, sa_len)) { + socklen_t sa_len; + if (!javaSocketAddressToSockaddr(env, javaSocketAddress, ss, sa_len)) { return -1; } - const sockaddr* to = (javaInetAddress != NULL) ? reinterpret_cast<const sockaddr*>(&ss) : NULL; - return NET_FAILURE_RETRY(env, ssize_t, sendto, javaFd, bytes.get() + byteOffset, byteCount, flags, to, sa_len); + + const sockaddr* sa = reinterpret_cast<const sockaddr*>(&ss); + // We don't need the return value because we'll already have thrown. + return NET_FAILURE_RETRY(env, ssize_t, sendto, javaFd, bytes.get() + byteOffset, byteCount, flags, sa, sa_len); } static void Posix_setegid(JNIEnv* env, jobject, jint egid) { @@ -1268,6 +1514,18 @@ static void Posix_setgid(JNIEnv* env, jobject, jint gid) { throwIfMinusOne(env, "setgid", TEMP_FAILURE_RETRY(setgid(gid))); } +static void Posix_setpgid(JNIEnv* env, jobject, jint pid, int pgid) { + throwIfMinusOne(env, "setpgid", TEMP_FAILURE_RETRY(setpgid(pid, pgid))); +} + +static void Posix_setregid(JNIEnv* env, jobject, jint rgid, int egid) { + throwIfMinusOne(env, "setregid", TEMP_FAILURE_RETRY(setregid(rgid, egid))); +} + +static void Posix_setreuid(JNIEnv* env, jobject, jint ruid, int euid) { + throwIfMinusOne(env, "setreuid", TEMP_FAILURE_RETRY(setreuid(ruid, euid))); +} + static jint Posix_setsid(JNIEnv* env, jobject) { return throwIfMinusOne(env, "setsid", TEMP_FAILURE_RETRY(setsid())); } @@ -1412,6 +1670,9 @@ static void Posix_shutdown(JNIEnv* env, jobject, jobject javaFd, jint how) { } static jobject Posix_socket(JNIEnv* env, jobject, jint domain, jint type, jint protocol) { + if (domain == AF_PACKET) { + protocol = htons(protocol); // Packet sockets specify the protocol in host byte order. + } int fd = throwIfMinusOne(env, "socket", TEMP_FAILURE_RETRY(socket(domain, type, protocol))); return fd != -1 ? jniCreateFileDescriptor(env, fd) : NULL; } @@ -1531,15 +1792,20 @@ static jint Posix_writev(JNIEnv* env, jobject, jobject javaFd, jobjectArray buff return IO_FAILURE_RETRY(env, ssize_t, writev, javaFd, ioVec.get(), ioVec.size()); } +#define NATIVE_METHOD_OVERLOAD(className, functionName, signature, variant) \ + { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName ## variant) } + static JNINativeMethod gMethods[] = { NATIVE_METHOD(Posix, accept, "(Ljava/io/FileDescriptor;Ljava/net/InetSocketAddress;)Ljava/io/FileDescriptor;"), NATIVE_METHOD(Posix, access, "(Ljava/lang/String;I)Z"), NATIVE_METHOD(Posix, android_getaddrinfo, "(Ljava/lang/String;Landroid/system/StructAddrinfo;I)[Ljava/net/InetAddress;"), NATIVE_METHOD(Posix, bind, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"), + NATIVE_METHOD_OVERLOAD(Posix, bind, "(Ljava/io/FileDescriptor;Ljava/net/SocketAddress;)V", SocketAddress), NATIVE_METHOD(Posix, chmod, "(Ljava/lang/String;I)V"), NATIVE_METHOD(Posix, chown, "(Ljava/lang/String;II)V"), NATIVE_METHOD(Posix, close, "(Ljava/io/FileDescriptor;)V"), NATIVE_METHOD(Posix, connect, "(Ljava/io/FileDescriptor;Ljava/net/InetAddress;I)V"), + NATIVE_METHOD_OVERLOAD(Posix, connect, "(Ljava/io/FileDescriptor;Ljava/net/SocketAddress;)V", SocketAddress), NATIVE_METHOD(Posix, dup, "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;"), NATIVE_METHOD(Posix, dup2, "(Ljava/io/FileDescriptor;I)Ljava/io/FileDescriptor;"), NATIVE_METHOD(Posix, environ, "()[Ljava/lang/String;"), @@ -1547,9 +1813,9 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(Posix, execve, "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V"), NATIVE_METHOD(Posix, fchmod, "(Ljava/io/FileDescriptor;I)V"), NATIVE_METHOD(Posix, fchown, "(Ljava/io/FileDescriptor;II)V"), - NATIVE_METHOD(Posix, fcntlVoid, "(Ljava/io/FileDescriptor;I)I"), - NATIVE_METHOD(Posix, fcntlLong, "(Ljava/io/FileDescriptor;IJ)I"), NATIVE_METHOD(Posix, fcntlFlock, "(Ljava/io/FileDescriptor;ILandroid/system/StructFlock;)I"), + NATIVE_METHOD(Posix, fcntlInt, "(Ljava/io/FileDescriptor;II)I"), + NATIVE_METHOD(Posix, fcntlVoid, "(Ljava/io/FileDescriptor;I)I"), NATIVE_METHOD(Posix, fdatasync, "(Ljava/io/FileDescriptor;)V"), NATIVE_METHOD(Posix, fstat, "(Ljava/io/FileDescriptor;)Landroid/system/StructStat;"), NATIVE_METHOD(Posix, fstatvfs, "(Ljava/io/FileDescriptor;)Landroid/system/StructStatVfs;"), @@ -1562,6 +1828,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(Posix, getenv, "(Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(Posix, getnameinfo, "(Ljava/net/InetAddress;I)Ljava/lang/String;"), NATIVE_METHOD(Posix, getpeername, "(Ljava/io/FileDescriptor;)Ljava/net/SocketAddress;"), + NATIVE_METHOD(Posix, getpgid, "(I)I"), NATIVE_METHOD(Posix, getpid, "()I"), NATIVE_METHOD(Posix, getppid, "()I"), NATIVE_METHOD(Posix, getpwnam, "(Ljava/lang/String;)Landroid/system/StructPasswd;"), @@ -1595,7 +1862,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(Posix, munlock, "(JJ)V"), NATIVE_METHOD(Posix, munmap, "(JJ)V"), NATIVE_METHOD(Posix, open, "(Ljava/lang/String;II)Ljava/io/FileDescriptor;"), - NATIVE_METHOD(Posix, pipe, "()[Ljava/io/FileDescriptor;"), + NATIVE_METHOD(Posix, pipe2, "(I)[Ljava/io/FileDescriptor;"), NATIVE_METHOD(Posix, poll, "([Landroid/system/StructPollfd;I)I"), NATIVE_METHOD(Posix, posix_fallocate, "(Ljava/io/FileDescriptor;JJ)V"), NATIVE_METHOD(Posix, prctl, "(IJJJJ)I"), @@ -1609,10 +1876,14 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(Posix, rename, "(Ljava/lang/String;Ljava/lang/String;)V"), NATIVE_METHOD(Posix, sendfile, "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Landroid/util/MutableLong;J)J"), NATIVE_METHOD(Posix, sendtoBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;IIILjava/net/InetAddress;I)I"), + NATIVE_METHOD_OVERLOAD(Posix, sendtoBytes, "(Ljava/io/FileDescriptor;Ljava/lang/Object;IIILjava/net/SocketAddress;)I", SocketAddress), NATIVE_METHOD(Posix, setegid, "(I)V"), NATIVE_METHOD(Posix, setenv, "(Ljava/lang/String;Ljava/lang/String;Z)V"), NATIVE_METHOD(Posix, seteuid, "(I)V"), NATIVE_METHOD(Posix, setgid, "(I)V"), + NATIVE_METHOD(Posix, setpgid, "(II)V"), + NATIVE_METHOD(Posix, setregid, "(II)V"), + NATIVE_METHOD(Posix, setreuid, "(II)V"), NATIVE_METHOD(Posix, setsid, "()I"), NATIVE_METHOD(Posix, setsockoptByte, "(Ljava/io/FileDescriptor;III)V"), NATIVE_METHOD(Posix, setsockoptIfreq, "(Ljava/io/FileDescriptor;IILjava/lang/String;)V"), diff --git a/luni/src/main/native/java_nio_charset_Charsets.cpp b/luni/src/main/native/libcore_util_CharsetUtils.cpp index a49ba22..57c8172 100644 --- a/luni/src/main/native/java_nio_charset_Charsets.cpp +++ b/luni/src/main/native/libcore_util_CharsetUtils.cpp @@ -245,6 +245,6 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(Charsets, toIsoLatin1Bytes, "([CII)[B"), NATIVE_METHOD(Charsets, toUtf8Bytes, "([CII)[B"), }; -void register_java_nio_charset_Charsets(JNIEnv* env) { - jniRegisterNativeMethods(env, "java/nio/charset/Charsets", gMethods, NELEM(gMethods)); +void register_libcore_util_CharsetUtils(JNIEnv* env) { + jniRegisterNativeMethods(env, "libcore/util/CharsetUtils", gMethods, NELEM(gMethods)); } diff --git a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp index 2ea8806..48defc1 100644 --- a/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp +++ b/luni/src/main/native/org_apache_harmony_xml_ExpatParser.cpp @@ -24,11 +24,12 @@ #include "ScopedPrimitiveArray.h" #include "ScopedStringChars.h" #include "ScopedUtfChars.h" -#include "UniquePtr.h" #include "jni.h" #include "cutils/log.h" #include "unicode/unistr.h" +#include <memory> + #include <string.h> #include <libexpat/expat.h> @@ -253,7 +254,7 @@ static int hashString(const char* s) { */ static InternedString* newInternedString(JNIEnv* env, const char* bytes, int hash) { // Allocate a new wrapper. - UniquePtr<InternedString> wrapper(new InternedString); + std::unique_ptr<InternedString> wrapper(new InternedString); if (wrapper.get() == NULL) { jniThrowOutOfMemoryError(env, NULL); return NULL; @@ -439,7 +440,7 @@ static size_t fillBuffer(ParsingContext* parsingContext, const char* utf8, int b return -1; } UErrorCode status = U_ZERO_ERROR; - UnicodeString utf16(UnicodeString::fromUTF8(StringPiece(utf8, byteCount))); + icu::UnicodeString utf16(icu::UnicodeString::fromUTF8(icu::StringPiece(utf8, byteCount))); return utf16.extract(chars.get(), byteCount, status); } @@ -962,7 +963,7 @@ static void notationDecl(void* data, const char* name, const char* /*base*/, con static jlong ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding, jboolean processNamespaces) { // Allocate parsing context. - UniquePtr<ParsingContext> context(new ParsingContext(object)); + std::unique_ptr<ParsingContext> context(new ParsingContext(object)); if (context.get() == NULL) { jniThrowOutOfMemoryError(env, NULL); return 0; diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk index 079ecd2..a90c683 100644 --- a/luni/src/main/native/sub.mk +++ b/luni/src/main/native/sub.mk @@ -28,7 +28,6 @@ LOCAL_SRC_FILES := \ java_lang_System.cpp \ java_math_NativeBN.cpp \ java_nio_ByteOrder.cpp \ - java_nio_charset_Charsets.cpp \ java_text_Bidi.cpp \ java_util_jar_StrictJarFile.cpp \ java_util_regex_Matcher.cpp \ @@ -38,9 +37,7 @@ LOCAL_SRC_FILES := \ java_util_zip_Deflater.cpp \ java_util_zip_Inflater.cpp \ libcore_icu_AlphabeticIndex.cpp \ - libcore_icu_DateIntervalFormat.cpp \ libcore_icu_ICU.cpp \ - libcore_icu_NativeBreakIterator.cpp \ libcore_icu_NativeCollation.cpp \ libcore_icu_NativeConverter.cpp \ libcore_icu_NativeDecimalFormat.cpp \ @@ -52,18 +49,12 @@ LOCAL_SRC_FILES := \ libcore_io_AsynchronousCloseMonitor.cpp \ libcore_io_Memory.cpp \ libcore_io_Posix.cpp \ + libcore_util_CharsetUtils.cpp \ org_apache_harmony_xml_ExpatParser.cpp \ readlink.cpp \ sun_misc_Unsafe.cpp \ valueOf.cpp \ -LOCAL_C_INCLUDES += \ - external/icu/icu4c/source/common \ - external/icu/icu4c/source/i18n \ - external/openssl/include \ - external/zlib \ - system/core/include \ - LOCAL_STATIC_LIBRARIES += \ libfdlibm \ diff --git a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java index 1c1296b..7adad72 100644 --- a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java +++ b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java @@ -16,10 +16,12 @@ package libcore.icu; -import java.util.Calendar; -import java.util.Locale; -import java.util.TimeZone; -import static libcore.icu.DateIntervalFormat.*; +import android.icu.util.Calendar; +import android.icu.util.TimeZone; +import android.icu.util.ULocale; + +import static libcore.icu.DateIntervalFormat.formatDateRange; +import static libcore.icu.DateUtilsBridge.*; public class DateIntervalFormatTest extends junit.framework.TestCase { private static final long MINUTE = 60 * 1000; @@ -32,7 +34,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { public void test_formatDateInterval() throws Exception { TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); - Calendar c = Calendar.getInstance(tz, Locale.US); + Calendar c = Calendar.getInstance(tz, ULocale.US); c.set(Calendar.MONTH, Calendar.JANUARY); c.set(Calendar.DAY_OF_MONTH, 19); c.set(Calendar.HOUR_OF_DAY, 3); @@ -51,10 +53,10 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { long noonDuration = (8 * 60 + 30) * 60 * 1000 - 15 * 1000; long midnightDuration = (3 * 60 + 30) * 60 * 1000 + 15 * 1000; - Locale de_DE = new Locale("de", "DE"); - Locale en_US = new Locale("en", "US"); - Locale es_ES = new Locale("es", "ES"); - Locale es_US = new Locale("es", "US"); + ULocale de_DE = new ULocale("de", "DE"); + ULocale en_US = new ULocale("en", "US"); + ULocale es_ES = new ULocale("es", "ES"); + ULocale es_US = new ULocale("es", "US"); assertEquals("Monday", formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_WEEKDAY)); assertEquals("January 19", formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR, FORMAT_SHOW_DATE)); @@ -81,11 +83,11 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { assertEquals("1/19/2009 – 2/9/2012", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19.1.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("19.01.2009 - 22.01.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("19.01.2009 - 22.04.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("19.01.2009 - 09.02.2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); + assertEquals("19.01.2009 – 22.01.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); + assertEquals("19.01.2009 – 22.04.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); + assertEquals("19.01.2009 – 09.02.2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("1/19/2009", formatDateRange(es_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); + assertEquals("19/1/2009", formatDateRange(es_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19/1/2009–22/1/2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19/1/2009–22/4/2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19/1/2009–9/2/2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); @@ -114,60 +116,61 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // The same tests but for de_DE. - assertEquals("19.-22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, 0)); - assertEquals("19.-22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("Mo., 19. - Do., 22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("Montag, 19. - Donnerstag, 22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); + assertEquals("19.–22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, 0)); + assertEquals("19.–22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("Mo., 19. – Do., 22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); - assertEquals("19. Januar - 22. April 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0)); - assertEquals("19. Jan. - 22. Apr. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("Mo., 19. Jan. - Mi., 22. Apr. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("Januar-April 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); + assertEquals("19. Januar – 22. April 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0)); + assertEquals("19. Jan. – 22. Apr. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("Mo., 19. Jan. – Mi., 22. Apr. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("Januar–April 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); - assertEquals("19. Jan. 2009 - 9. Feb. 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("Jan. 2009 - Feb. 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); - assertEquals("19. Januar 2009 - 9. Februar 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0)); - assertEquals("Montag, 19. Januar 2009 - Donnerstag, 9. Februar 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); + assertEquals("19. Jan. 2009 – 9. Feb. 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("Jan. 2009 – Feb. 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); + assertEquals("19. Januar 2009 – 9. Februar 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0)); + assertEquals("Montag, 19. Januar 2009 – Donnerstag, 9. Februar 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); // The same tests but for es_US. - assertEquals("19–22 enero 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, 0)); - assertEquals("19–22 ene. 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene.–jue., 22 ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("lunes, 19 enero–jueves, 22 enero de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); + assertEquals("19–22 de enero de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, 0)); + assertEquals("19 – 22 de ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("lun., 19 de ene. – jue., 22 de ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); - assertEquals("19 enero–22 abril de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0)); - assertEquals("19 ene.–22 abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene.–mié., 22 abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("19 de enero–22 de abril de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0)); + assertEquals("19 de ene. – 22 de abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("lun., 19 de ene. – mié., 22 de abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("enero–abril de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); - assertEquals("19 ene. de 2009–9 feb. de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("ene. 2009–feb. 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); - assertEquals("19 enero de 2009–9 febrero de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0)); - assertEquals("lunes, 19 enero de 2009–jueves, 9 febrero de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); + assertEquals("19 de ene. de 2009 – 9 de feb. de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("ene. de 2009 – feb. de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); + + assertEquals("19 de enero de 2009–9 de febrero de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0)); + assertEquals("lunes, 19 de enero de 2009–jueves, 9 de febrero de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); // The same tests but for es_ES. - assertEquals("19–22 enero 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0)); + assertEquals("19–22 de enero de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0)); assertEquals("19–22 ene. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene.–jue., 22 ene. de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("lunes, 19 enero–jueves, 22 enero de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); + assertEquals("lun., 19 ene.–jue., 22 ene. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); - assertEquals("19 enero–22 abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0)); - assertEquals("19 ene.–22 abr. de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene.–mié., 22 abr. de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("19 de enero–22 de abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0)); + assertEquals("19 ene.–22 abr. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("lun., 19 ene.–mié., 22 abr. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("enero–abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); - assertEquals("19 ene. de 2009–9 feb. de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("19 ene. 2009–9 feb. 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); assertEquals("ene. 2009–feb. 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); - assertEquals("19 enero de 2009–9 febrero de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0)); - assertEquals("lunes, 19 enero de 2009–jueves, 9 febrero de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); + assertEquals("19 de enero de 2009–9 de febrero de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0)); + assertEquals("lunes, 19 de enero de 2009–jueves, 9 de febrero de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); } // http://b/8862241 - we should be able to format dates past 2038. // See also http://code.google.com/p/android/issues/detail?id=13050. public void test8862241() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); Calendar c = Calendar.getInstance(tz, l); c.clear(); @@ -181,7 +184,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10089890 - we should take the given time zone into account. public void test10089890() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); TimeZone pacific = TimeZone.getTimeZone("America/Los_Angeles"); int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL | FORMAT_SHOW_TIME | FORMAT_24HOUR; @@ -203,7 +206,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { int abbr12 = time12 | FORMAT_ABBREV_ALL; int abbr24 = time24 | FORMAT_ABBREV_ALL; - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); // Full length on-the-hour times. @@ -230,7 +233,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10560853 - when the time is not displayed, an end time 0 ms into the next day is // considered to belong to the previous day. public void test10560853_when_time_not_displayed() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); long midnight = 0; @@ -254,7 +257,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10560853 - when the start and end times are otherwise on the same day, // an end time 0 ms into the next day is considered to belong to the previous day. public void test10560853_for_single_day_events() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE; @@ -266,7 +269,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // The fix for http://b/10560853 didn't work except for the day around the epoch, which was // all the unit test checked! public void test_single_day_events_later_than_epoch() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE; @@ -284,7 +287,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // The fix for http://b/10560853 didn't work except for UTC, which was // all the unit test checked! public void test_single_day_events_not_in_UTC() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone pacific = TimeZone.getTimeZone("America/Los_Angeles"); int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE; @@ -305,7 +308,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10209343 - even if the caller didn't explicitly ask us to include the year, // we should do so for years other than the current year. public void test10209343_when_not_this_year() { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_TIME | FORMAT_24HOUR; @@ -328,7 +331,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10209343 - for the current year, we should honor the FORMAT_SHOW_YEAR flags. public void test10209343_when_this_year() { // Construct a date in the current year (whenever the test happens to be run). - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); Calendar c = Calendar.getInstance(utc, l); c.set(Calendar.MONTH, Calendar.FEBRUARY); @@ -363,7 +366,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/8467515 - yet another y2k38 bug report. public void test8467515() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH | FORMAT_ABBREV_WEEKDAY; long t; @@ -383,7 +386,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/12004664 public void test12004664() throws Exception { TimeZone utc = TimeZone.getTimeZone("UTC"); - Calendar c = Calendar.getInstance(utc, Locale.US); + Calendar c = Calendar.getInstance(utc, ULocale.US); c.clear(); c.set(Calendar.YEAR, 1980); c.set(Calendar.MONTH, Calendar.FEBRUARY); @@ -392,26 +395,26 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { long thisYear = c.getTimeInMillis(); int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_YEAR; - assertEquals("Sunday, February 10, 1980", formatDateRange(new Locale("en", "US"), utc, thisYear, thisYear, flags)); + assertEquals("Sunday, February 10, 1980", formatDateRange(new ULocale("en", "US"), utc, thisYear, thisYear, flags)); - // If we supported non-Gregorian calendars, this is what that we'd expect for these locales. + // If we supported non-Gregorian calendars, this is what that we'd expect for these ULocales. // This is really the correct behavior, but since java.util.Calendar currently only supports // the Gregorian calendar, we want to deliberately force icu4c to agree, otherwise we'd have // a mix of calendars throughout an app's UI depending on whether Java or native code formatted // the date. - // assertEquals("یکشنبه ۲۱ بهمن ۱۳۵۸ ه.ش.", formatDateRange(new Locale("fa"), utc, thisYear, thisYear, flags)); - // assertEquals("AP ۱۳۵۸ سلواغه ۲۱, یکشنبه", formatDateRange(new Locale("ps"), utc, thisYear, thisYear, flags)); - // assertEquals("วันอาทิตย์ 10 กุมภาพันธ์ 2523", formatDateRange(new Locale("th"), utc, thisYear, thisYear, flags)); + // assertEquals("یکشنبه ۲۱ بهمن ۱۳۵۸ ه.ش.", formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags)); + // assertEquals("AP ۱۳۵۸ سلواغه ۲۱, یکشنبه", formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags)); + // assertEquals("วันอาทิตย์ 10 กุมภาพันธ์ 2523", formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags)); // For now, here are the localized Gregorian strings instead... - assertEquals("یکشنبه ۱۰ فوریهٔ ۱۹۸۰", formatDateRange(new Locale("fa"), utc, thisYear, thisYear, flags)); - assertEquals("یکشنبه د ۱۹۸۰ د فبروري ۱۰", formatDateRange(new Locale("ps"), utc, thisYear, thisYear, flags)); - assertEquals("วันอาทิตย์ 10 กุมภาพันธ์ 1980", formatDateRange(new Locale("th"), utc, thisYear, thisYear, flags)); + assertEquals("یکشنبه ۱۰ فوریهٔ ۱۹۸۰", formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags)); + assertEquals("یکشنبه د ۱۹۸۰ د فبروري ۱۰", formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags)); + assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ ค.ศ. 1980", formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags)); } // http://b/13234532 public void test13234532() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL | FORMAT_12HOUR; diff --git a/luni/src/test/java/libcore/icu/ICUTest.java b/luni/src/test/java/libcore/icu/ICUTest.java index a7cc7a0..99679a7 100644 --- a/luni/src/test/java/libcore/icu/ICUTest.java +++ b/luni/src/test/java/libcore/icu/ICUTest.java @@ -20,6 +20,7 @@ import java.text.BreakIterator; import java.text.Collator; import java.util.Arrays; import java.util.Locale; +import libcore.util.ZoneInfoDB; public class ICUTest extends junit.framework.TestCase { public void test_getISOLanguages() throws Exception { @@ -152,24 +153,24 @@ public class ICUTest extends junit.framework.TestCase { assertEquals("sr_ME_#Latn", sr_Latn_ME.toString()); assertEquals("Latn", sr_Latn_ME.getScript()); - assertEquals("Српски", sr_Cyrl_BA.getDisplayLanguage(sr_Cyrl_BA)); + assertEquals("српски", sr_Cyrl_BA.getDisplayLanguage(sr_Cyrl_BA)); assertEquals("Босна и Херцеговина", sr_Cyrl_BA.getDisplayCountry(sr_Cyrl_BA)); - assertEquals("Ћирилица", sr_Cyrl_BA.getDisplayScript(sr_Cyrl_BA)); + assertEquals("ћирилица", sr_Cyrl_BA.getDisplayScript(sr_Cyrl_BA)); assertEquals("", sr_Cyrl_BA.getDisplayVariant(sr_Cyrl_BA)); - assertEquals("Српски", sr_Cyrl_ME.getDisplayLanguage(sr_Cyrl_ME)); + assertEquals("српски", sr_Cyrl_ME.getDisplayLanguage(sr_Cyrl_ME)); assertEquals("Црна Гора", sr_Cyrl_ME.getDisplayCountry(sr_Cyrl_ME)); - assertEquals("Ћирилица", sr_Cyrl_ME.getDisplayScript(sr_Cyrl_ME)); + assertEquals("ћирилица", sr_Cyrl_ME.getDisplayScript(sr_Cyrl_ME)); assertEquals("", sr_Cyrl_ME.getDisplayVariant(sr_Cyrl_ME)); - assertEquals("Srpski", sr_Latn_BA.getDisplayLanguage(sr_Latn_BA)); + assertEquals("srpski", sr_Latn_BA.getDisplayLanguage(sr_Latn_BA)); assertEquals("Bosna i Hercegovina", sr_Latn_BA.getDisplayCountry(sr_Latn_BA)); - assertEquals("Latinica", sr_Latn_BA.getDisplayScript(sr_Latn_BA)); + assertEquals("latinica", sr_Latn_BA.getDisplayScript(sr_Latn_BA)); assertEquals("", sr_Latn_BA.getDisplayVariant(sr_Latn_BA)); - assertEquals("Srpski", sr_Latn_ME.getDisplayLanguage(sr_Latn_ME)); + assertEquals("srpski", sr_Latn_ME.getDisplayLanguage(sr_Latn_ME)); assertEquals("Crna Gora", sr_Latn_ME.getDisplayCountry(sr_Latn_ME)); - assertEquals("Latinica", sr_Latn_ME.getDisplayScript(sr_Latn_ME)); + assertEquals("latinica", sr_Latn_ME.getDisplayScript(sr_Latn_ME)); assertEquals("", sr_Latn_ME.getDisplayVariant(sr_Latn_ME)); assertEquals("BIH", sr_Cyrl_BA.getISO3Country()); @@ -253,4 +254,11 @@ public class ICUTest extends junit.framework.TestCase { ICU.setDefaultLocale(initialDefaultLocale); } } + + /** Confirms that ICU agrees with the rest of libcore about the version of the TZ data in use. */ + public void testTimeZoneDataVersion() { + String icu4cTzVersion = ICU.getTZDataVersion(); + String zoneInfoTzVersion = ZoneInfoDB.getInstance().getVersion(); + assertEquals(icu4cTzVersion, zoneInfoTzVersion); + } } diff --git a/luni/src/test/java/libcore/icu/LocaleDataTest.java b/luni/src/test/java/libcore/icu/LocaleDataTest.java index 0a83c53..09c3f0f 100644 --- a/luni/src/test/java/libcore/icu/LocaleDataTest.java +++ b/luni/src/test/java/libcore/icu/LocaleDataTest.java @@ -24,7 +24,7 @@ public class LocaleDataTest extends junit.framework.TestCase { for (Locale l : Locale.getAvailableLocales()) { LocaleData d = LocaleData.get(l); // System.err.format("%20s %s %s %s\n", l, d.yesterday, d.today, d.tomorrow); - // System.err.format("%20s %10s %10s\n", l, d.timeFormat12, d.timeFormat24); + // System.err.format("%20s %10s %10s\n", l, d.timeFormat_hm, d.timeFormat_Hm); } } @@ -73,7 +73,7 @@ public class LocaleDataTest extends junit.framework.TestCase { assertEquals("leden", l.longStandAloneMonthNames[0]); assertEquals("led", l.shortStandAloneMonthNames[0]); - assertEquals("l", l.tinyStandAloneMonthNames[0]); + assertEquals("1", l.tinyStandAloneMonthNames[0]); } public void test_ko_KR() throws Exception { @@ -124,11 +124,11 @@ public class LocaleDataTest extends junit.framework.TestCase { // http://b/7924970 public void testTimeFormat12And24() throws Exception { LocaleData en_US = LocaleData.get(Locale.US); - assertEquals("h:mm a", en_US.timeFormat12); - assertEquals("HH:mm", en_US.timeFormat24); + assertEquals("h:mm a", en_US.timeFormat_hm); + assertEquals("HH:mm", en_US.timeFormat_Hm); LocaleData ja_JP = LocaleData.get(Locale.JAPAN); - assertEquals("aK:mm", ja_JP.timeFormat12); - assertEquals("H:mm", ja_JP.timeFormat24); + assertEquals("aK:mm", ja_JP.timeFormat_hm); + assertEquals("H:mm", ja_JP.timeFormat_Hm); } } diff --git a/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java b/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java new file mode 100644 index 0000000..101896f --- /dev/null +++ b/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java @@ -0,0 +1,667 @@ +/* + * 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.icu; + +import android.icu.util.ULocale; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +import static libcore.icu.DateUtilsBridge.FORMAT_ABBREV_ALL; +import static libcore.icu.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; +import static libcore.icu.DateUtilsBridge.FORMAT_NO_YEAR; +import static libcore.icu.DateUtilsBridge.FORMAT_NUMERIC_DATE; +import static libcore.icu.DateUtilsBridge.FORMAT_SHOW_YEAR; +import static libcore.icu.RelativeDateTimeFormatter.DAY_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.HOUR_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.MINUTE_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.SECOND_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.WEEK_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.YEAR_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.getRelativeDateTimeString; +import static libcore.icu.RelativeDateTimeFormatter.getRelativeTimeSpanString; + +public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { + + // Tests adopted from CTS tests for DateUtils.getRelativeTimeSpanString. + public void test_getRelativeTimeSpanStringCTS() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("GMT"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 GMT + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long baseTime = cal.getTimeInMillis(); + + assertEquals("0 minutes ago", + getRelativeTimeSpanString(en_US, tz, baseTime - SECOND_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + assertEquals("In 0 minutes", + getRelativeTimeSpanString(en_US, tz, baseTime + SECOND_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + + assertEquals("1 minute ago", + getRelativeTimeSpanString(en_US, tz, 0, MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 0)); + assertEquals("In 1 minute", + getRelativeTimeSpanString(en_US, tz, MINUTE_IN_MILLIS, 0, MINUTE_IN_MILLIS, 0)); + + assertEquals("42 minutes ago", + getRelativeTimeSpanString(en_US, tz, baseTime - 42 * MINUTE_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + assertEquals("In 42 minutes", + getRelativeTimeSpanString(en_US, tz, baseTime + 42 * MINUTE_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + + final long TWO_HOURS_IN_MS = 2 * HOUR_IN_MILLIS; + assertEquals("2 hours ago", + getRelativeTimeSpanString(en_US, tz, baseTime - TWO_HOURS_IN_MS, baseTime, + MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE)); + assertEquals("In 2 hours", + getRelativeTimeSpanString(en_US, tz, baseTime + TWO_HOURS_IN_MS, baseTime, + MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE)); + + assertEquals("In 42 min.", + getRelativeTimeSpanString(en_US, tz, baseTime + (42 * MINUTE_IN_MILLIS), baseTime, + MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + + assertEquals("Tomorrow", + getRelativeTimeSpanString(en_US, tz, DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0)); + assertEquals("In 2 days", + getRelativeTimeSpanString(en_US, tz, 2 * DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0)); + assertEquals("Yesterday", + getRelativeTimeSpanString(en_US, tz, 0, DAY_IN_MILLIS, DAY_IN_MILLIS, 0)); + assertEquals("2 days ago", + getRelativeTimeSpanString(en_US, tz, 0, 2 * DAY_IN_MILLIS, DAY_IN_MILLIS, 0)); + + final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000; + assertEquals("5 days ago", + getRelativeTimeSpanString(en_US, tz, baseTime - DAY_DURATION, baseTime, + DAY_IN_MILLIS, 0)); + } + + private void test_getRelativeTimeSpanString_helper(long delta, long minResolution, int flags, + String expectedInPast, + String expectedInFuture) throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 PST + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long base = cal.getTimeInMillis(); + + assertEquals(expectedInPast, + getRelativeTimeSpanString(en_US, tz, base - delta, base, minResolution, flags)); + assertEquals(expectedInFuture, + getRelativeTimeSpanString(en_US, tz, base + delta, base, minResolution, flags)); + } + + private void test_getRelativeTimeSpanString_helper(long delta, long minResolution, + String expectedInPast, + String expectedInFuture) throws Exception { + test_getRelativeTimeSpanString_helper(delta, minResolution, 0, expectedInPast, expectedInFuture); + } + + public void test_getRelativeTimeSpanString() throws Exception { + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, "0 seconds ago", "0 seconds ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago", "In 1 minute"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago", "In 1 minute"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, "5 days ago", "In 5 days"); + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "0 seconds ago", + "0 seconds ago"); + test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 second ago", + "In 1 second"); + test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "2 seconds ago", + "In 2 seconds"); + test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "25 seconds ago", + "In 25 seconds"); + test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 hour ago", + "In 1 hour"); + + test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "0 minutes ago", + "0 minutes ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "2 minutes ago", + "In 2 minutes"); + test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "25 minutes ago", + "In 25 minutes"); + test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "12 hours ago", + "In 12 hours"); + + test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago", + "0 hours ago"); + test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "2 hours ago", + "In 2 hours"); + test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "5 hours ago", + "In 5 hours"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "20 hours ago", + "In 20 hours"); + + test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, "January 11", + "March 2"); + + test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago", + "0 weeks ago"); + test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "2 weeks ago", + "In 2 weeks"); + test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "25 weeks ago", + "In 25 weeks"); + + // duration >= minResolution + test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, "30 seconds ago", + "In 30 seconds"); + test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, + "30 minutes ago", "In 30 minutes"); + test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, "5 days ago", + "In 5 days"); + test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, "July 10, 2014", + "September 3"); + test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, + "February 6, 2010", "February 4, 2020"); + + test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, + "1 minute ago", "In 1 minute"); + test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today"); + test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Today"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + + // duration < minResolution + test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, "0 minutes ago", + "In 0 minutes"); + test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago", + "In 0 hours"); + test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, "0 hours ago", + "In 0 hours"); + test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago", + "In 0 weeks"); + test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, "0 weeks ago", + "In 0 weeks"); + } + + public void test_getRelativeTimeSpanStringAbbrev() throws Exception { + int flags = FORMAT_ABBREV_RELATIVE; + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, flags, "0 sec. ago", + "0 sec. ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, flags, "1 min. ago", + "In 1 min."); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, flags, "5 days ago", "In 5 days"); + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "0 sec. ago", "0 sec. ago"); + test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 sec. ago", "In 1 sec."); + test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "2 sec. ago", "In 2 sec."); + test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "25 sec. ago", "In 25 sec."); + test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + + test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "0 min. ago", "0 min. ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "2 min. ago", "In 2 min."); + test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "25 min. ago", "In 25 min."); + test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "12 hr. ago", "In 12 hr."); + + test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "0 hr. ago", "0 hr. ago"); + test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "2 hr. ago", "In 2 hr."); + test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "5 hr. ago", "In 5 hr."); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "20 hr. ago", "In 20 hr."); + + test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, "Today", + "Today"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, + "January 11", "March 2"); + + test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "0 wk. ago", "0 wk. ago"); + test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "2 wk. ago", "In 2 wk."); + test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "25 wk. ago", "In 25 wk."); + + // duration >= minResolution + test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, flags, "30 sec. ago", + "In 30 sec."); + test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "30 min. ago", "In 30 min."); + test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "5 days ago", "In 5 days"); + test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "July 10, 2014", "September 3"); + test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "February 6, 2010", "February 4, 2020"); + + test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, "Today", + "Today"); + test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Today"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + + // duration < minResolution + test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "0 min. ago", "In 0 min."); + test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags, + "0 hr. ago", "In 0 hr."); + test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, flags, + "0 hr. ago", "In 0 hr."); + test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, flags, + "0 wk. ago", "In 0 wk."); + test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, flags, + "0 wk. ago", "In 0 wk."); + + } + + public void test_getRelativeTimeSpanStringGerman() throws Exception { + // Bug: 19744876 + // We need to specify the timezone and the time explicitly. Otherwise it + // may not always give a correct answer of "tomorrow" by using + // (now + DAY_IN_MILLIS). + Locale de_DE = new Locale("de", "DE"); + TimeZone tz = TimeZone.getTimeZone("Europe/Berlin"); + Calendar cal = Calendar.getInstance(tz, de_DE); + // Feb 5, 2015 at 10:50 CET + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long now = cal.getTimeInMillis(); + + // 42 minutes ago + assertEquals("Vor 42 Minuten", getRelativeTimeSpanString(de_DE, tz, + now - 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0)); + // In 42 minutes + assertEquals("In 42 Minuten", getRelativeTimeSpanString(de_DE, tz, + now + 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0)); + // Yesterday + assertEquals("Gestern", getRelativeTimeSpanString(de_DE, tz, + now - DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day before yesterday + assertEquals("Vorgestern", getRelativeTimeSpanString(de_DE, tz, + now - 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // Tomorrow + assertEquals("Morgen", getRelativeTimeSpanString(de_DE, tz, + now + DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day after tomorrow + assertEquals("Übermorgen", getRelativeTimeSpanString(de_DE, tz, + now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + } + + public void test_getRelativeTimeSpanStringFrench() throws Exception { + Locale fr_FR = new Locale("fr", "FR"); + TimeZone tz = TimeZone.getTimeZone("Europe/Paris"); + Calendar cal = Calendar.getInstance(tz, fr_FR); + // Feb 5, 2015 at 10:50 CET + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long now = cal.getTimeInMillis(); + + // 42 minutes ago + assertEquals("Il y a 42 minutes", getRelativeTimeSpanString(fr_FR, tz, + now - (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0)); + // In 42 minutes + assertEquals("Dans 42 minutes", getRelativeTimeSpanString(fr_FR, tz, + now + (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0)); + // Yesterday + assertEquals("Hier", getRelativeTimeSpanString(fr_FR, tz, + now - DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day before yesterday + assertEquals("Avant-hier", getRelativeTimeSpanString(fr_FR, tz, + now - 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // Tomorrow + assertEquals("Demain", getRelativeTimeSpanString(fr_FR, tz, + now + DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day after tomorrow + assertEquals("Après-demain", getRelativeTimeSpanString(fr_FR, tz, + now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + } + + // Tests adopted from CTS tests for DateUtils.getRelativeDateTimeString. + public void test_getRelativeDateTimeStringCTS() throws Exception { + Locale en_US = Locale.getDefault(); + TimeZone tz = TimeZone.getDefault(); + final long baseTime = System.currentTimeMillis(); + + final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000; + assertNotNull(getRelativeDateTimeString(en_US, tz, baseTime - DAY_DURATION, baseTime, + MINUTE_IN_MILLIS, DAY_IN_MILLIS, + FORMAT_NUMERIC_DATE)); + } + + public void test_getRelativeDateTimeString() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 PST + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long base = cal.getTimeInMillis(); + + assertEquals("5 seconds ago, 10:49 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0, + MINUTE_IN_MILLIS, 0)); + assertEquals("5 min. ago, 10:45 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0, + HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("0 hr. ago, 10:45 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 hours ago, 5:50 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, 0)); + assertEquals("Yesterday, 7:50 PM", + getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 days ago, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + YEAR_IN_MILLIS, 0)); + + // User-supplied flags should be ignored when formatting the date clause. + final int FORMAT_SHOW_WEEKDAY = 0x00002; + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, + FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY)); + } + + public void test_getRelativeDateTimeStringDST() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + + // DST starts on Mar 9, 2014 at 2:00 AM. + // So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'. + cal.set(2014, Calendar.MARCH, 9, 3, 15, 0); + long base = cal.getTimeInMillis(); + assertEquals("Yesterday, 9:15 PM", + getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + // 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'. + cal.set(2014, Calendar.MARCH, 9, 2, 0, 0); + base = cal.getTimeInMillis(); + assertEquals("In 1 hour, 4:00 AM", + getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + // DST ends on Nov 2, 2014 at 2:00 AM. Clocks are turned backward 1 hour to + // 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'. + cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0); + base = cal.getTimeInMillis(); + assertEquals("Yesterday, 10:20 PM", + getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0); + base = cal.getTimeInMillis(); + // 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'. + assertEquals("In 45 minutes, 1:30 AM", + getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + // 45 minutes later, it should be 'In 45 minutes, 1:15 AM'. + assertEquals("In 45 minutes, 1:15 AM", + getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS, + base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0)); + // Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'. + assertEquals("In 45 minutes, 2:00 AM", + getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS, + base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0)); + } + + + public void test_getRelativeDateTimeStringItalian() throws Exception { + Locale it_IT = new Locale("it", "IT"); + TimeZone tz = TimeZone.getTimeZone("Europe/Rome"); + Calendar cal = Calendar.getInstance(tz, it_IT); + // 05 febbraio 2015 20:15 + cal.set(2015, Calendar.FEBRUARY, 5, 20, 15, 0); + final long base = cal.getTimeInMillis(); + + assertEquals("5 secondi fa, 20:14", + getRelativeDateTimeString(it_IT, tz, base - 5 * SECOND_IN_MILLIS, base, 0, + MINUTE_IN_MILLIS, 0)); + assertEquals("5 min. fa, 20:10", + getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base, 0, + HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("0 h. fa, 20:10", + getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("Ieri, 22:15", + getRelativeDateTimeString(it_IT, tz, base - 22 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 giorni fa, 20:15", + getRelativeDateTimeString(it_IT, tz, base - 5 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("27/11/2014, 20:15", + getRelativeDateTimeString(it_IT, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + } + + // http://b/5252772: detect the actual date difference + public void test5252772() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + + // Now is Sep 2, 2011, 10:23 AM PDT. + Calendar nowCalendar = Calendar.getInstance(tz, en_US); + nowCalendar.set(2011, Calendar.SEPTEMBER, 2, 10, 23, 0); + final long now = nowCalendar.getTimeInMillis(); + + // Sep 1, 2011, 10:24 AM + Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US); + yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0); + long yesterday1 = yesterdayCalendar1.getTimeInMillis(); + assertEquals("Yesterday, 10:24 AM", + getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 1, 2011, 10:22 AM + Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US); + yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0); + long yesterday2 = yesterdayCalendar2.getTimeInMillis(); + assertEquals("Yesterday, 10:22 AM", + getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Aug 31, 2011, 10:24 AM + Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US); + twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0); + long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis(); + assertEquals("2 days ago, 10:24 AM", + getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Aug 31, 2011, 10:22 AM + Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US); + twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0); + long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis(); + assertEquals("2 days ago, 10:22 AM", + getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 3, 2011, 10:22 AM + Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US); + tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0); + long tomorrow1 = tomorrowCalendar1.getTimeInMillis(); + assertEquals("Tomorrow, 10:22 AM", + getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 3, 2011, 10:24 AM + Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US); + tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0); + long tomorrow2 = tomorrowCalendar2.getTimeInMillis(); + assertEquals("Tomorrow, 10:24 AM", + getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 4, 2011, 10:22 AM + Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US); + twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0); + long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis(); + assertEquals("In 2 days, 10:22 AM", + getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 4, 2011, 10:24 AM + Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US); + twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0); + long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis(); + assertEquals("In 2 days, 10:24 AM", + getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + } + + // b/19822016: show / hide the year based on the dates in the arguments. + public void test_bug19822016() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2012 at 10:50 PST + cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0); + long base = cal.getTimeInMillis(); + + assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + assertEquals("January 6, 2012", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + + // Feb 5, 2018 at 10:50 PST + cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0); + base = cal.getTimeInMillis(); + assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + assertEquals("January 6, 2018", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + } +} diff --git a/luni/src/test/java/libcore/io/OsTest.java b/luni/src/test/java/libcore/io/OsTest.java index a0d1e5a..e648e8a 100644 --- a/luni/src/test/java/libcore/io/OsTest.java +++ b/luni/src/test/java/libcore/io/OsTest.java @@ -16,18 +16,26 @@ package libcore.io; +import android.system.ErrnoException; +import android.system.NetlinkSocketAddress; +import android.system.PacketSocketAddress; +import android.system.StructTimeval; import android.system.StructUcred; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.InetUnixAddress; +import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Locale; import junit.framework.TestCase; import static android.system.OsConstants.*; @@ -44,6 +52,20 @@ public class OsTest extends TestCase { s.close(); } + public void testFcntlInt() throws Exception { + File f = File.createTempFile("OsTest", "tst"); + FileInputStream fis = null; + try { + fis = new FileInputStream(f); + Libcore.os.fcntlInt(fis.getFD(), F_SETFD, FD_CLOEXEC); + int flags = Libcore.os.fcntlVoid(fis.getFD(), F_GETFD); + assertTrue((flags & FD_CLOEXEC) != 0); + } finally { + IoUtils.closeQuietly(fis); + f.delete(); + } + } + public void testUnixDomainSockets_in_file_system() throws Exception { String path = System.getProperty("java.io.tmpdir") + "/test_unix_socket"; new File(path).delete(); @@ -196,9 +218,10 @@ public class OsTest extends TestCase { fis.close(); } - public void test_byteBufferPositions_sendto_recvfrom() throws Exception { - final FileDescriptor serverFd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); - Libcore.os.bind(serverFd, InetAddress.getLoopbackAddress(), 0); + static void checkByteBufferPositions_sendto_recvfrom( + int family, InetAddress loopback) throws Exception { + final FileDescriptor serverFd = Libcore.os.socket(family, SOCK_STREAM, 0); + Libcore.os.bind(serverFd, loopback, 0); Libcore.os.listen(serverFd, 5); InetSocketAddress address = (InetSocketAddress) Libcore.os.getsockname(serverFd); @@ -232,7 +255,7 @@ public class OsTest extends TestCase { server.start(); - FileDescriptor clientFd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + FileDescriptor clientFd = Libcore.os.socket(family, SOCK_STREAM, 0); Libcore.os.connect(clientFd, address.getAddress(), address.getPort()); final byte[] bytes = "good bye, cruel black hole with fancy distortion".getBytes(StandardCharsets.US_ASCII); @@ -254,4 +277,146 @@ public class OsTest extends TestCase { Libcore.os.close(clientFd); } + + public void test_NetlinkSocket() throws Exception { + FileDescriptor nlSocket = Libcore.os.socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + Libcore.os.bind(nlSocket, new NetlinkSocketAddress()); + NetlinkSocketAddress address = (NetlinkSocketAddress) Libcore.os.getsockname(nlSocket); + assertTrue(address.getPortId() > 0); + assertEquals(0, address.getGroupsMask()); + + NetlinkSocketAddress nlKernel = new NetlinkSocketAddress(); + Libcore.os.connect(nlSocket, nlKernel); + NetlinkSocketAddress nlPeer = (NetlinkSocketAddress) Libcore.os.getpeername(nlSocket); + assertEquals(0, nlPeer.getPortId()); + assertEquals(0, nlPeer.getGroupsMask()); + Libcore.os.close(nlSocket); + } + + public void test_PacketSocketAddress() throws Exception { + NetworkInterface lo = NetworkInterface.getByName("lo"); + FileDescriptor fd = Libcore.os.socket(AF_PACKET, SOCK_DGRAM, ETH_P_IPV6); + PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IPV6, lo.getIndex()); + Libcore.os.bind(fd, addr); + + PacketSocketAddress bound = (PacketSocketAddress) Libcore.os.getsockname(fd); + assertEquals((short) ETH_P_IPV6, bound.sll_protocol); // ETH_P_IPV6 is an int. + assertEquals(lo.getIndex(), bound.sll_ifindex); + assertEquals(ARPHRD_LOOPBACK, bound.sll_hatype); + assertEquals(0, bound.sll_pkttype); + + // The loopback address is ETH_ALEN bytes long and is all zeros. + // http://lxr.free-electrons.com/source/drivers/net/loopback.c?v=3.10#L167 + assertEquals(6, bound.sll_addr.length); + for (int i = 0; i < 6; i++) { + assertEquals(0, bound.sll_addr[i]); + } + } + + public void test_byteBufferPositions_sendto_recvfrom_af_inet() throws Exception { + checkByteBufferPositions_sendto_recvfrom(AF_INET, InetAddress.getByName("127.0.0.1")); + } + + public void test_byteBufferPositions_sendto_recvfrom_af_inet6() throws Exception { + checkByteBufferPositions_sendto_recvfrom(AF_INET6, InetAddress.getByName("::1")); + } + + private void checkSendToSocketAddress(int family, InetAddress loopback) throws Exception { + FileDescriptor recvFd = Libcore.os.socket(family, SOCK_DGRAM, 0); + Libcore.os.bind(recvFd, loopback, 0); + StructTimeval tv = StructTimeval.fromMillis(20); + Libcore.os.setsockoptTimeval(recvFd, SOL_SOCKET, SO_RCVTIMEO, tv); + + InetSocketAddress to = ((InetSocketAddress) Libcore.os.getsockname(recvFd)); + FileDescriptor sendFd = Libcore.os.socket(family, SOCK_DGRAM, 0); + byte[] msg = ("Hello, I'm going to a socket address: " + to.toString()).getBytes("UTF-8"); + int len = msg.length; + + assertEquals(len, Libcore.os.sendto(sendFd, msg, 0, len, 0, to)); + byte[] received = new byte[msg.length + 42]; + InetSocketAddress from = new InetSocketAddress(); + assertEquals(len, Libcore.os.recvfrom(recvFd, received, 0, received.length, 0, from)); + assertEquals(loopback, from.getAddress()); + } + + public void test_sendtoSocketAddress_af_inet() throws Exception { + checkSendToSocketAddress(AF_INET, InetAddress.getByName("127.0.0.1")); + } + + public void test_sendtoSocketAddress_af_inet6() throws Exception { + checkSendToSocketAddress(AF_INET6, InetAddress.getByName("::1")); + } + + public void test_socketFamilies() throws Exception { + FileDescriptor fd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("::"), 0); + InetSocketAddress localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet6Address.ANY, localSocketAddress.getAddress()); + + fd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("0.0.0.0"), 0); + localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet6Address.ANY, localSocketAddress.getAddress()); + + fd = Libcore.os.socket(AF_INET, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("0.0.0.0"), 0); + localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet4Address.ANY, localSocketAddress.getAddress()); + try { + Libcore.os.bind(fd, InetAddress.getByName("::"), 0); + fail("Expected ErrnoException binding IPv4 socket to ::"); + } catch (ErrnoException expected) { + assertEquals("Expected EAFNOSUPPORT binding IPv4 socket to ::", EAFNOSUPPORT, expected.errno); + } + } + + private static void assertArrayEquals(byte[] expected, byte[] actual) { + assertTrue("Expected=" + Arrays.toString(expected) + ", actual=" + Arrays.toString(actual), + Arrays.equals(expected, actual)); + } + + private static void checkSocketPing(FileDescriptor fd, InetAddress to, byte[] packet, + byte type, byte responseType, boolean useSendto) throws Exception { + int len = packet.length; + packet[0] = type; + if (useSendto) { + assertEquals(len, Libcore.os.sendto(fd, packet, 0, len, 0, to, 0)); + } else { + Libcore.os.connect(fd, to, 0); + assertEquals(len, Libcore.os.sendto(fd, packet, 0, len, 0, null, 0)); + } + + int icmpId = ((InetSocketAddress) Libcore.os.getsockname(fd)).getPort(); + byte[] received = new byte[4096]; + InetSocketAddress srcAddress = new InetSocketAddress(); + assertEquals(len, Libcore.os.recvfrom(fd, received, 0, received.length, 0, srcAddress)); + assertEquals(to, srcAddress.getAddress()); + assertEquals(responseType, received[0]); + assertEquals(received[4], (byte) (icmpId >> 8)); + assertEquals(received[5], (byte) (icmpId & 0xff)); + + received = Arrays.copyOf(received, len); + received[0] = (byte) type; + received[2] = received[3] = 0; // Checksum. + received[4] = received[5] = 0; // ICMP ID. + assertArrayEquals(packet, received); + } + + public void test_socketPing() throws Exception { + final byte ICMP_ECHO = 8, ICMP_ECHOREPLY = 0; + final byte ICMPV6_ECHO_REQUEST = (byte) 128, ICMPV6_ECHO_REPLY = (byte) 129; + final byte[] packet = ("\000\000\000\000" + // ICMP type, code. + "\000\000\000\003" + // ICMP ID (== port), sequence number. + "Hello myself").getBytes(StandardCharsets.US_ASCII); + + FileDescriptor fd = Libcore.os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + InetAddress ipv6Loopback = InetAddress.getByName("::1"); + checkSocketPing(fd, ipv6Loopback, packet, ICMPV6_ECHO_REQUEST, ICMPV6_ECHO_REPLY, true); + checkSocketPing(fd, ipv6Loopback, packet, ICMPV6_ECHO_REQUEST, ICMPV6_ECHO_REPLY, false); + + fd = Libcore.os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + InetAddress ipv4Loopback = InetAddress.getByName("127.0.0.1"); + checkSocketPing(fd, ipv4Loopback, packet, ICMP_ECHO, ICMP_ECHOREPLY, true); + checkSocketPing(fd, ipv4Loopback, packet, ICMP_ECHO, ICMP_ECHOREPLY, false); + } } diff --git a/luni/src/test/java/libcore/java/io/FileDescriptorTest.java b/luni/src/test/java/libcore/java/io/FileDescriptorTest.java index 39472df..e2780c9 100644 --- a/luni/src/test/java/libcore/java/io/FileDescriptorTest.java +++ b/luni/src/test/java/libcore/java/io/FileDescriptorTest.java @@ -30,14 +30,14 @@ public class FileDescriptorTest extends TestCase { new RandomAccessFile(f, "r").getFD().sync(); } - public void test_isSocket() throws Exception { + public void test_isSocket$() throws Exception { File f = new File("/dev/null"); FileInputStream fis = new FileInputStream(f); - assertFalse(fis.getFD().isSocket()); + assertFalse(fis.getFD().isSocket$()); fis.close(); ServerSocket s = new ServerSocket(); - assertTrue(s.getImpl$().getFD$().isSocket()); + assertTrue(s.getImpl$().getFD$().isSocket$()); s.close(); } } diff --git a/luni/src/test/java/libcore/java/io/FileInputStreamTest.java b/luni/src/test/java/libcore/java/io/FileInputStreamTest.java index 14950ee..26de11a 100644 --- a/luni/src/test/java/libcore/java/io/FileInputStreamTest.java +++ b/luni/src/test/java/libcore/java/io/FileInputStreamTest.java @@ -67,7 +67,7 @@ public final class FileInputStreamTest extends TestCase { } public void testSkipInPipes() throws Exception { - FileDescriptor[] pipe = Libcore.os.pipe(); + FileDescriptor[] pipe = Libcore.os.pipe2(0); DataFeeder feeder = new DataFeeder(pipe[1]); try { feeder.start(); diff --git a/luni/src/test/java/libcore/java/io/RandomAccessFileTest.java b/luni/src/test/java/libcore/java/io/RandomAccessFileTest.java index afe49b7..8d99457 100644 --- a/luni/src/test/java/libcore/java/io/RandomAccessFileTest.java +++ b/luni/src/test/java/libcore/java/io/RandomAccessFileTest.java @@ -20,6 +20,8 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; + import junit.framework.TestCase; import libcore.java.lang.ref.FinalizationTester; @@ -73,6 +75,61 @@ public final class RandomAccessFileTest extends TestCase { FinalizationTester.induceFinalization(); } } + + // http://b/19892782 + public void testCloseRaf_sameChannelReturned() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + + FileChannel fileChannelBeforeClosing = raf.getChannel(); + raf.close(); + FileChannel fileChannelAfterClosing = raf.getChannel(); + assertSame(fileChannelBeforeClosing, fileChannelAfterClosing); + } + + // http://b/19892782 + public void testCloseRaf_channelIsClosed() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + + FileChannel fileChannelBeforeClosing = raf.getChannel(); + raf.close(); + FileChannel fileChannelAfterClosing = raf.getChannel(); + assertFalse(fileChannelBeforeClosing.isOpen()); + } + + // http://b/19892782 + public void testCloseFileChannel_sameChannelReturned() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + + FileChannel fileChannelBeforeClosing = raf.getChannel(); + fileChannelBeforeClosing.close(); + + FileChannel fileChannelAfterClosing = raf.getChannel(); + assertSame(fileChannelBeforeClosing, fileChannelAfterClosing); + } + + // http://b/19892782 + public void testCloseFileChannel_returnedFileChannelIsClosed() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + + FileChannel fileChannelBeforeClosing = raf.getChannel(); + // This should close the Raf, and previous implementations wrongly returned a new + // open (but useless) channel in this case. + fileChannelBeforeClosing.close(); + FileChannel fileChannelAfterClosing = raf.getChannel(); + assertFalse(fileChannelBeforeClosing.isOpen()); + } + + // http://b/19892782 + public void testCloseRafBeforeGetChannel_returnChannelWithCloseFdAfterClose() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + raf.close(); + try { + raf.getChannel().size(); + fail(); + } catch (IOException expected) { + } + } + private void createRandomAccessFile(File file) throws Exception { // TODO: fix our register maps and remove this otherwise unnecessary // indirection! (http://b/5412580) diff --git a/luni/src/test/java/libcore/java/lang/FloatTest.java b/luni/src/test/java/libcore/java/lang/FloatTest.java index 92e7ae4..c25bd5c 100644 --- a/luni/src/test/java/libcore/java/lang/FloatTest.java +++ b/luni/src/test/java/libcore/java/lang/FloatTest.java @@ -121,4 +121,20 @@ public class FloatTest extends junit.framework.TestCase { } assertEquals(f1, 0f); } + + // Float equivalent of testParseLargestSubnormalDoublePrecision. http://b/18087920. + public void testParseLargestSubnormalFloatPrecision() { + // These are different ways of saying MIN_NORMAL. + assertEquals(1.1754943508222875e-38f, Float.parseFloat("1.1754943508222875e-38")); + assertEquals(1.1754943508222875e-38f, Float.parseFloat("0.00011754943508222875e-34f")); + assertEquals(1.1754943508222875e-38f, Float.parseFloat("00000001.1754943508222875e-38f")); + assertEquals(1.1754943508222875e-38f, Float.parseFloat("1.17549435082228750000e-38f")); + assertEquals(1.1754943508222875e-38f, Float.parseFloat("1.1754943508222875e-0038f")); + assertEquals(-1.1754943508222875e-38f, Float.parseFloat("-1.1754943508222875e-38f")); + + // Extra interesting values suggested as part of http://b/18087920. + assertEquals(1.1754944e-38f, Float.parseFloat("11754942807573643E-54")); + assertEquals(1.1754944e-38f, Float.parseFloat("11754942807573644E-54")); + assertEquals(1.1754944e-38f, Float.parseFloat("11754942807573645E-54")); + } } diff --git a/luni/src/test/java/libcore/java/lang/OldAndroidMonitorTest.java b/luni/src/test/java/libcore/java/lang/OldAndroidMonitorTest.java index d1704fb..4760c6d 100644..100755 --- a/luni/src/test/java/libcore/java/lang/OldAndroidMonitorTest.java +++ b/luni/src/test/java/libcore/java/lang/OldAndroidMonitorTest.java @@ -103,7 +103,7 @@ public class OldAndroidMonitorTest extends TestCase { } private class Interrupter extends Thread { - Waiter waiter; + private final Waiter waiter; Interrupter(String name, Waiter waiter) { super(name); @@ -119,8 +119,7 @@ public class OldAndroidMonitorTest extends TestCase { } } - void run_inner() { - waiter.spin = true; + private void run_inner() { // System.out.println("InterruptTest: starting waiter"); waiter.start(); @@ -168,7 +167,7 @@ public class OldAndroidMonitorTest extends TestCase { private class Waiter extends Thread { Object interrupterLock = new Object(); - Boolean spin = false; + volatile boolean spin = true; Waiter(String name) { super(name); @@ -188,6 +187,7 @@ public class OldAndroidMonitorTest extends TestCase { while (spin) { // We're going to get interrupted while we spin. } + if (interrupted()) { // System.out.println("Waiter done spinning; interrupted."); } else { @@ -196,7 +196,7 @@ public class OldAndroidMonitorTest extends TestCase { } synchronized (this) { - Boolean sawEx = false; + boolean sawEx = false; try { synchronized (interrupterLock) { @@ -216,7 +216,7 @@ public class OldAndroidMonitorTest extends TestCase { } } synchronized (this) { - Boolean sawEx = false; + boolean sawEx = false; try { synchronized (interrupterLock) { @@ -236,7 +236,7 @@ public class OldAndroidMonitorTest extends TestCase { } } synchronized (this) { - Boolean sawEx = false; + boolean sawEx = false; try { synchronized (interrupterLock) { diff --git a/luni/src/test/java/libcore/java/lang/OldClassTest.java b/luni/src/test/java/libcore/java/lang/OldClassTest.java index 23a42bd..f5bc787 100644 --- a/luni/src/test/java/libcore/java/lang/OldClassTest.java +++ b/luni/src/test/java/libcore/java/lang/OldClassTest.java @@ -40,11 +40,6 @@ import tests.support.resource.Support_Resources; @SuppressWarnings("deprecation") public class OldClassTest extends junit.framework.TestCase { - - public static final String FILENAME = - OldClassTest.class.getPackage().getName().replace('.', '/') + - "/test#.properties"; - final String packageName = getClass().getPackage().getName(); final String classNameInitError1 = packageName + ".TestClass1"; final String classNameInitError2 = packageName + ".TestClass1B"; diff --git a/luni/src/test/java/libcore/java/lang/PackageTest.java b/luni/src/test/java/libcore/java/lang/PackageTest.java index 6e274a0..c004e23 100644 --- a/luni/src/test/java/libcore/java/lang/PackageTest.java +++ b/luni/src/test/java/libcore/java/lang/PackageTest.java @@ -25,9 +25,10 @@ public final class PackageTest extends TestCase { private static final List<Package> packages = Arrays.asList(Package.getPackages()); public void test_getAnnotations() throws Exception { - // Package annotations aren't supported, but pre-ICS we crashed. - assertEquals(0, getClass().getPackage().getAnnotations().length); - assertEquals(0, getClass().getPackage().getDeclaredAnnotations().length); + // Pre-ICS we crashed. To pass, the package-info and TestPackageAnnotation classes must be + // on the classpath. + assertEquals(1, getClass().getPackage().getAnnotations().length); + assertEquals(1, getClass().getPackage().getDeclaredAnnotations().length); } public void testGetPackage() { diff --git a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java index 9766cef..51aed38 100644 --- a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java +++ b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java @@ -28,7 +28,7 @@ import static tests.support.Support_Exec.execAndCheckOutput; public class ProcessBuilderTest extends AbstractResourceLeakageDetectorTestCase { private static String shell() { - String deviceSh = "/system/bin/sh"; + String deviceSh = System.getenv("ANDROID_ROOT") + "/bin/sh"; String desktopSh = "/bin/sh"; return new File(deviceSh).exists() ? deviceSh : desktopSh; } diff --git a/luni/src/test/java/libcore/java/lang/TestPackageAnnotation.java b/luni/src/test/java/libcore/java/lang/TestPackageAnnotation.java new file mode 100644 index 0000000..7626206 --- /dev/null +++ b/luni/src/test/java/libcore/java/lang/TestPackageAnnotation.java @@ -0,0 +1,26 @@ +/* + * 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.java.lang; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +// Used by PackageTest +@Target(ElementType.PACKAGE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestPackageAnnotation {} diff --git a/luni/src/test/java/libcore/java/lang/package-info.java b/luni/src/test/java/libcore/java/lang/package-info.java new file mode 100644 index 0000000..d916e9a --- /dev/null +++ b/luni/src/test/java/libcore/java/lang/package-info.java @@ -0,0 +1,18 @@ +/* + * 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. + */ +// Used by PackageTest +@TestPackageAnnotation +package libcore.java.lang;
\ No newline at end of file diff --git a/luni/src/test/java/libcore/java/lang/reflect/FieldTest.java b/luni/src/test/java/libcore/java/lang/reflect/FieldTest.java index b60d984..75665db 100644 --- a/luni/src/test/java/libcore/java/lang/reflect/FieldTest.java +++ b/luni/src/test/java/libcore/java/lang/reflect/FieldTest.java @@ -17,6 +17,8 @@ package libcore.java.lang.reflect; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + import junit.framework.TestCase; public final class FieldTest extends TestCase { @@ -46,6 +48,56 @@ public final class FieldTest extends TestCase { assertFalse(f1.equals(f2)); } + // Tests that the "synthetic" modifier is handled correctly. + // It's supposed to be present but not shown in toString. + public void testSyntheticModifier() throws NoSuchFieldException { + Field valuesField = Thread.State.class.getDeclaredField("$VALUES"); + // Check that this test makes sense. + assertTrue(valuesField.isSynthetic()); + assertEquals(Modifier.SYNTHETIC, valuesField.getModifiers() & Modifier.SYNTHETIC); + assertEquals("private static final java.lang.Thread$State[] java.lang.Thread$State.$VALUES", + valuesField.toString()); + } + + // Ensure that the "enum constant" bit is not returned in toString. + public void testEnumValueField() throws NoSuchFieldException { + Field blockedField = Thread.State.class.getDeclaredField("BLOCKED"); + assertTrue(Thread.State.class.getDeclaredField("BLOCKED").isEnumConstant()); + assertEquals("public static final", Modifier.toString(blockedField.getModifiers())); + assertEquals( + "public static final java.lang.Thread$State java.lang.Thread$State.BLOCKED", + blockedField.toString()); + } + + class ClassWithATransientField { + private transient Class<String> transientField = String.class; + } + + // Tests that the "transient" modifier is handled correctly. + // The underlying constant value for it is the same as for the "varargs" method modifier. + // http://b/18488857 + public void testTransientModifier() throws NoSuchFieldException { + Field transientField = ClassWithATransientField.class.getDeclaredField("transientField"); + // Check that this test makes sense. + assertEquals(Modifier.TRANSIENT, transientField.getModifiers() & Modifier.TRANSIENT); + assertEquals( + "private transient java.lang.Class " + + "libcore.java.lang.reflect.FieldTest$ClassWithATransientField" + + ".transientField", + transientField.toString()); + } + + public void testToGenericString() throws NoSuchFieldException { + Field transientField = ClassWithATransientField.class.getDeclaredField("transientField"); + // Check that this test makes sense. + assertEquals(Modifier.TRANSIENT, transientField.getModifiers() & Modifier.TRANSIENT); + assertEquals( + "private transient java.lang.Class<java.lang.String> " + + "libcore.java.lang.reflect.FieldTest$ClassWithATransientField" + + ".transientField", + transientField.toGenericString()); + } + static class FieldTestHelper { public String a; public Object b; diff --git a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java index c3a436c..a3f9065 100644 --- a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java +++ b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java @@ -17,6 +17,7 @@ package libcore.java.lang.reflect; import java.lang.reflect.Method; + import junit.framework.TestCase; public final class MethodTest extends TestCase { @@ -197,6 +198,23 @@ public final class MethodTest extends TestCase { assertEquals( "public java.lang.Process java.lang.Runtime.exec(java.lang.String[])" + " throws java.io.IOException", Runtime.class.getMethod("exec", new Class[] { String[].class }).toString()); + // http://b/18488857 + assertEquals( + "public int java.lang.String.compareTo(java.lang.Object)", + String.class.getMethod("compareTo", Object.class).toString()); + } + + // Tests that the "varargs" modifier is handled correctly. + // The underlying constant value for it is the same as for the "transient" field modifier. + // http://b/18488857 + public void testVarargsModifier() throws NoSuchMethodException { + Method stringFormatMethod = String.class.getMethod( + "format", new Class[] { String.class, Object[].class }); + assertTrue(stringFormatMethod.isVarArgs()); + assertEquals( + "public static java.lang.String java.lang.String.format(" + + "java.lang.String,java.lang.Object[])", + stringFormatMethod.toString()); } public static class MethodTestHelper { diff --git a/luni/src/test/java/libcore/java/lang/reflect/ModifierTest.java b/luni/src/test/java/libcore/java/lang/reflect/ModifierTest.java index 1bde157..0505f2f 100644 --- a/luni/src/test/java/libcore/java/lang/reflect/ModifierTest.java +++ b/luni/src/test/java/libcore/java/lang/reflect/ModifierTest.java @@ -100,6 +100,9 @@ public class ModifierTest extends junit.framework.TestCase { } public void test_toStringI() { - assertEquals("public abstract", Modifier.toString(Modifier.PUBLIC | Modifier.ABSTRACT)); + // Note that it checks that "STRICT" is rendered as "strictfp" (for other modifiers, + // the displayed name is the same as the lowercase constant name). + assertEquals("public abstract strictfp", + Modifier.toString(Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STRICT)); } } diff --git a/luni/src/test/java/libcore/java/net/InetAddressTest.java b/luni/src/test/java/libcore/java/net/InetAddressTest.java index 4b656cc..8bdcf64 100644 --- a/luni/src/test/java/libcore/java/net/InetAddressTest.java +++ b/luni/src/test/java/libcore/java/net/InetAddressTest.java @@ -21,10 +21,14 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.UnknownHostException; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import libcore.util.SerializationTester; public class InetAddressTest extends junit.framework.TestCase { + private static final byte[] LOOPBACK4_BYTES = new byte[] { 127, 0, 0, 1 }; private static final byte[] LOOPBACK6_BYTES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; private static final String[] INVALID_IPv4_NUMERIC_ADDRESSES = new String[] { @@ -72,7 +76,7 @@ public class InetAddressTest extends junit.framework.TestCase { } private static Inet6Address localhost6() throws Exception { - return (Inet6Address) InetAddress.getByAddress("localhost", LOOPBACK6_BYTES); + return (Inet6Address) InetAddress.getByAddress("ip6-localhost", LOOPBACK6_BYTES); } public void test_parseNumericAddress() throws Exception { @@ -311,4 +315,84 @@ public class InetAddressTest extends junit.framework.TestCase { assertEquals(resultStrings[i], result); } } + + public void test_getHostNameCaches() throws Exception { + InetAddress inetAddress = InetAddress.getByAddress(LOOPBACK6_BYTES); + assertEquals("::1", inetAddress.getHostString()); + assertEquals("ip6-localhost", inetAddress.getHostName()); + // getHostString() should now be different. + assertEquals("ip6-localhost", inetAddress.getHostString()); + } + + public void test_getByAddress_loopbackIpv4() throws Exception { + InetAddress inetAddress = InetAddress.getByAddress(LOOPBACK4_BYTES); + assertEquals(LOOPBACK4_BYTES, "localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getByAddress_loopbackIpv6() throws Exception { + InetAddress inetAddress = InetAddress.getByAddress(LOOPBACK6_BYTES); + assertEquals(LOOPBACK6_BYTES, "ip6-localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getByName_loopbackIpv4() throws Exception { + InetAddress inetAddress = InetAddress.getByName("127.0.0.1"); + assertEquals(LOOPBACK4_BYTES, "localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getByName_loopbackIpv6() throws Exception { + InetAddress inetAddress = InetAddress.getByName("::1"); + assertEquals(LOOPBACK6_BYTES, "ip6-localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getAllByName_localhost() throws Exception { + InetAddress[] inetAddresses = InetAddress.getAllByName("localhost"); + assertEquals(1, inetAddresses.length); + InetAddress inetAddress = inetAddresses[0]; + assertEquals(LOOPBACK4_BYTES, "localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getAllByName_ip6_localhost() throws Exception { + InetAddress[] inetAddresses = InetAddress.getAllByName("ip6-localhost"); + assertEquals(1, inetAddresses.length); + InetAddress inetAddress = inetAddresses[0]; + assertEquals(LOOPBACK6_BYTES, "ip6-localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getByName_null() throws Exception { + InetAddress inetAddress = InetAddress.getByName("::1"); + + Set<InetAddress> expectedLoopbackAddresses = + createSet(Inet4Address.LOOPBACK, Inet6Address.LOOPBACK); + assertTrue(expectedLoopbackAddresses.contains(inetAddress)); + } + + public void test_getAllByName_null() throws Exception { + InetAddress[] inetAddresses = InetAddress.getAllByName(null); + assertEquals(2, inetAddresses.length); + Set<InetAddress> expectedLoopbackAddresses = + createSet(Inet4Address.LOOPBACK, Inet6Address.LOOPBACK); + assertEquals(expectedLoopbackAddresses, createSet(inetAddresses)); + } + + private static void assertEquals( + byte[] expectedAddressBytes, String expectedHostname, InetAddress actual) { + assertArrayEquals(expectedAddressBytes, actual.getAddress()); + assertEquals(expectedHostname, actual.getHostName()); + + } + + private static void assertArrayEquals(byte[] expected, byte[] actual) { + assertTrue("Expected=" + Arrays.toString(expected) + ", actual=" + Arrays.toString(actual), + Arrays.equals(expected, actual)); + } + + private static Set<InetAddress> createSet(InetAddress... members) { + return new HashSet<InetAddress>(Arrays.asList(members)); + } } diff --git a/luni/src/test/java/libcore/java/net/InetSocketAddressTest.java b/luni/src/test/java/libcore/java/net/InetSocketAddressTest.java index 3bca8dc..d97c48a 100644 --- a/luni/src/test/java/libcore/java/net/InetSocketAddressTest.java +++ b/luni/src/test/java/libcore/java/net/InetSocketAddressTest.java @@ -63,7 +63,7 @@ public class InetSocketAddressTest extends TestCase { } InetSocketAddress isa = new InetSocketAddress((InetAddress)null, 80); - assertEquals("0.0.0.0", isa.getHostName()); + assertEquals("::", isa.getHostName()); try { new InetSocketAddress(InetAddress.getByName("localhost"), 65536); @@ -80,7 +80,7 @@ public class InetSocketAddressTest extends TestCase { public void test_ConstructorI() { InetSocketAddress isa = new InetSocketAddress(65535); - assertEquals("0.0.0.0", isa.getHostName()); + assertEquals("::", isa.getHostName()); assertEquals(65535, isa.getPort()); try { @@ -150,6 +150,20 @@ public class InetSocketAddressTest extends TestCase { assertTrue(hasHostname.isUnresolved()); assertEquals("some host", hasHostname.getHostString()); assertEquals("some host", hasHostname.getHostName()); + + InetSocketAddress hasHostnameAndAddress = new InetSocketAddress( + InetAddress.getByAddress("some host", new byte[] { 127, 0, 0, 1 }), + 1234); + assertFalse(hasHostnameAndAddress.isUnresolved()); + assertEquals("some host", hasHostnameAndAddress.getHostString()); + assertEquals("some host", hasHostnameAndAddress.getHostName()); + + // Using a host name that is actually an IP. + InetSocketAddress hostnameIsIp = InetSocketAddress.createUnresolved("127.0.0.1", 1234); + assertTrue(hostnameIsIp.isUnresolved()); + assertEquals("127.0.0.1", hostnameIsIp.getHostString()); + assertEquals("127.0.0.1", hostnameIsIp.getHostName()); + // When we don't have a hostname, whether or not we do the reverse lookup is the difference // between getHostString and getHostName... InetAddress address = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }); @@ -157,4 +171,18 @@ public class InetSocketAddressTest extends TestCase { assertEquals("127.0.0.1", noHostname.getHostString()); assertEquals("localhost", noHostname.getHostName()); } + + public void test_getHostString_cachingBehavior() throws Exception { + InetAddress inetAddress = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }); + InetSocketAddress socketAddress = new InetSocketAddress(inetAddress, 1234); + assertEquals("127.0.0.1", socketAddress.getHostString()); + assertEquals("localhost", socketAddress.getHostName()); + assertEquals("localhost", socketAddress.getHostString()); + + inetAddress = InetAddress.getByName("127.0.0.1"); + socketAddress = new InetSocketAddress(inetAddress, 1234); + assertEquals("127.0.0.1", socketAddress.getHostString()); + assertEquals("localhost", socketAddress.getHostName()); + assertEquals("localhost", socketAddress.getHostString()); + } } diff --git a/luni/src/test/java/libcore/java/net/OldSocketTest.java b/luni/src/test/java/libcore/java/net/OldSocketTest.java index 7973965..ded5802 100644 --- a/luni/src/test/java/libcore/java/net/OldSocketTest.java +++ b/luni/src/test/java/libcore/java/net/OldSocketTest.java @@ -37,6 +37,7 @@ import java.net.UnknownHostException; import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.SocketChannel; import java.security.Permission; +import tests.net.StuckServer; import tests.support.Support_Configuration; public class OldSocketTest extends OldSocketTestCase { @@ -932,25 +933,15 @@ public class OldSocketTest extends OldSocketTestCase { } // start by validating the error checks - int portNumber = 0; - Socket theSocket = null; - ServerSocket serverSocket = null; - SocketAddress theAddress = null; - SocketAddress nonConnectableAddress = null; - SocketAddress nonReachableAddress = null; - SocketAddress invalidType = null; - // byte[] theBytes = {-1,-1,-1,-1}; - byte[] theBytes = { 0, 0, 0, 0 }; - theAddress = new InetSocketAddress(InetAddress.getLocalHost(), - portNumber); - nonConnectableAddress = new InetSocketAddress(InetAddress - .getByAddress(theBytes), portNumber); - nonReachableAddress = new InetSocketAddress(InetAddress - .getByName(Support_Configuration.ResolvedNotExistingHost), - portNumber); - invalidType = new mySocketAddress(); + byte[] theBytes = { 0, 0, 0, 0 }; + SocketAddress theAddress = new InetSocketAddress(InetAddress.getLocalHost(), 0); + SocketAddress nonConnectableAddress = new InetSocketAddress(InetAddress.getByAddress(theBytes), 0); + SocketAddress nonReachableAddress = new InetSocketAddress(StuckServer.UNREACHABLE_ADDRESS, 0); + SocketAddress invalidType = new mySocketAddress(); + Socket theSocket = null; + ServerSocket serverSocket = null; try { theSocket = new Socket(); theSocket.connect(null); @@ -1165,7 +1156,7 @@ public class OldSocketTest extends OldSocketTestCase { byte[] theBytes = { 0, 0, 0, 0 }; SocketAddress theAddress = new InetSocketAddress(InetAddress.getLocalHost(), 0); SocketAddress nonConnectableAddress = new InetSocketAddress(InetAddress.getByAddress(theBytes), 0); - SocketAddress nonReachableAddress = new InetSocketAddress(InetAddress.getByName(Support_Configuration.ResolvedNotExistingHost), 0); + SocketAddress nonReachableAddress = new InetSocketAddress(StuckServer.UNREACHABLE_ADDRESS, 0); SocketAddress invalidType = new mySocketAddress(); Socket theSocket = null; diff --git a/luni/src/test/java/libcore/java/net/SocketTest.java b/luni/src/test/java/libcore/java/net/SocketTest.java index fb09be0..9765a45 100644 --- a/luni/src/test/java/libcore/java/net/SocketTest.java +++ b/luni/src/test/java/libcore/java/net/SocketTest.java @@ -31,9 +31,11 @@ import java.net.SocketImpl; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; public class SocketTest extends junit.framework.TestCase { // See http://b/2980559. @@ -353,6 +355,37 @@ public class SocketTest extends junit.framework.TestCase { assertEquals(boundAddress.getPort(), localAddressAfterClose.getPort()); } + public void testCloseDuringConnect() throws Exception { + final CountDownLatch signal = new CountDownLatch(1); + + final Socket s = new Socket(); + new Thread() { + @Override + public void run() { + try { + // This address is reserved for documentation: should never be reachable. + InetSocketAddress unreachableIp = new InetSocketAddress("192.0.2.0", 80); + // This should never return. + s.connect(unreachableIp, 0 /* infinite */); + fail("Connect returned unexpectedly for: " + unreachableIp); + } catch (SocketException expected) { + assertTrue(expected.getMessage().contains("Socket closed")); + signal.countDown(); + } catch (IOException e) { + fail("Unexpected exception: " + e); + } + } + }.start(); + + // Wait for the connect() thread to run and start connect() + Thread.sleep(2000); + + s.close(); + + boolean connectUnblocked = signal.await(2000, TimeUnit.MILLISECONDS); + assertTrue(connectUnblocked); + } + static class MockServer { private ExecutorService executor; private ServerSocket serverSocket; diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java index c09939f..3f831e0 100644 --- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java +++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java @@ -16,7 +16,8 @@ package libcore.java.net; -import com.android.okhttp.HttpResponseCache; +import com.android.okhttp.AndroidShimResponseCache; + import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.google.mockwebserver.RecordedRequest; @@ -37,11 +38,14 @@ import java.net.ProtocolException; import java.net.Proxy; import java.net.ResponseCache; import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.net.UnknownHostException; +import java.nio.channels.SocketChannel; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -58,18 +62,18 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import javax.net.SocketFactory; +import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; -import libcore.java.security.StandardNames; import libcore.java.security.TestKeyStore; import libcore.java.util.AbstractResourceLeakageDetectorTestCase; import libcore.javax.net.ssl.TestSSLContext; @@ -84,7 +88,7 @@ import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; public final class URLConnectionTest extends AbstractResourceLeakageDetectorTestCase { private MockWebServer server; - private HttpResponseCache cache; + private AndroidShimResponseCache cache; private String hostName; @Override protected void setUp() throws Exception { @@ -674,11 +678,144 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest private void initResponseCache() throws IOException { String tmp = System.getProperty("java.io.tmpdir"); File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); - cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); + cache = AndroidShimResponseCache.create(cacheDir, Integer.MAX_VALUE); ResponseCache.setDefault(cache); } /** + * Test Etag headers are returned correctly when a client-side cache is not installed. + * https://code.google.com/p/android/issues/detail?id=108949 + */ + public void testEtagHeaders_uncached() throws Exception { + final String etagValue1 = "686897696a7c876b7e"; + final String body1 = "Response with etag 1"; + final String etagValue2 = "686897696a7c876b7f"; + final String body2 = "Response with etag 2"; + + server.enqueue( + new MockResponse() + .setBody(body1) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue1)); + server.enqueue( + new MockResponse() + .setBody(body2) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue2)); + server.play(); + + URL url = server.getUrl("/"); + HttpURLConnection connection1 = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue1, connection1.getHeaderField("Etag")); + assertContent(body1, connection1); + connection1.disconnect(); + + // Discard the server-side record of the request made. + server.takeRequest(); + + HttpURLConnection connection2 = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue2, connection2.getHeaderField("Etag")); + assertContent(body2, connection2); + connection2.disconnect(); + + // Check the client did not cache. + RecordedRequest request = server.takeRequest(); + assertNull(request.getHeader("If-None-Match")); + } + + /** + * Test Etag headers are returned correctly when a client-side cache is installed and the server + * data is unchanged. + * https://code.google.com/p/android/issues/detail?id=108949 + */ + public void testEtagHeaders_cachedWithServerHit() throws Exception { + final String etagValue = "686897696a7c876b7e"; + final String body = "Response with etag"; + + server.enqueue( + new MockResponse() + .setBody(body) + .setResponseCode(HttpURLConnection.HTTP_OK) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue)); + + server.enqueue( + new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.play(); + + initResponseCache(); + + URL url = server.getUrl("/"); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue, connection.getHeaderField("Etag")); + assertContent(body, connection); + connection.disconnect(); + + // Discard the server-side record of the request made. + server.takeRequest(); + + // Confirm the cached body is returned along with the original etag header. + HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue, cachedConnection.getHeaderField("Etag")); + assertContent(body, cachedConnection); + cachedConnection.disconnect(); + + // Check the client formatted the request correctly. + RecordedRequest request = server.takeRequest(); + assertEquals(etagValue, request.getHeader("If-None-Match")); + } + + /** + * Test Etag headers are returned correctly when a client-side cache is installed and the server + * data has changed. + * https://code.google.com/p/android/issues/detail?id=108949 + */ + public void testEtagHeaders_cachedWithServerMiss() throws Exception { + final String etagValue1 = "686897696a7c876b7e"; + final String body1 = "Response with etag 1"; + final String etagValue2 = "686897696a7c876b7f"; + final String body2 = "Response with etag 2"; + + server.enqueue( + new MockResponse() + .setBody(body1) + .setResponseCode(HttpURLConnection.HTTP_OK) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue1)); + + server.enqueue( + new MockResponse() + .setBody(body2) + .setResponseCode(HttpURLConnection.HTTP_OK) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue2)); + + server.play(); + + initResponseCache(); + + URL url = server.getUrl("/"); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue1, connection.getHeaderField("Etag")); + assertContent(body1, connection); + connection.disconnect(); + + // Discard the server-side record of the request made. + server.takeRequest(); + + // Confirm the new body is returned along with the new etag header. + HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue2, cachedConnection.getHeaderField("Etag")); + assertContent(body2, cachedConnection); + cachedConnection.disconnect(); + + // Check the client formatted the request correctly. + RecordedRequest request = server.takeRequest(); + assertEquals(etagValue1, request.getHeader("If-None-Match")); + } + + /** * Test which headers are sent unencrypted to the HTTP proxy. */ public void testProxyConnectIncludesProxyHeadersOnly() @@ -2074,6 +2211,19 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest connection.disconnect(); } + public void testLastModified() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Last-Modified", "Wed, 27 Nov 2013 11:26:00 GMT") + .setBody("Hello")); + server.play(); + + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.connect(); + + assertEquals(1385551560000L, connection.getLastModified()); + assertEquals(1385551560000L, connection.getHeaderFieldDate("Last-Modified", -1)); + } + public void testClientSendsContentLength() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); @@ -2185,52 +2335,107 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest urlConnection.getInputStream(); } - public void testSslFallback() throws Exception { + public void testSslFallback_allSupportedProtocols() throws Exception { TestSSLContext testSSLContext = TestSSLContext.create(); - // This server socket factory only supports SSLv3. This is to avoid issues due to SCSV - // checks. See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 + String[] allSupportedProtocols = { "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3" }; SSLSocketFactory serverSocketFactory = new LimitedProtocolsSocketFactory( testSSLContext.serverContext.getSocketFactory(), - "SSLv3"); - + allSupportedProtocols); server.useHttps(serverSocketFactory, false); server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); - server.enqueue(new MockResponse().setBody("This required a 2nd handshake")); + server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); + server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); + server.enqueue(new MockResponse().setBody("This required fallbacks")); server.play(); HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); - // Keep track of the client sockets created so that we can interrogate them. - RecordingSocketFactory clientSocketFactory = - new RecordingSocketFactory(testSSLContext.clientContext.getSocketFactory()); + // Keeps track of the client sockets created so that we can interrogate them. + final boolean disableFallbackScsv = true; + FallbackTestClientSocketFactory clientSocketFactory = new FallbackTestClientSocketFactory( + new LimitedProtocolsSocketFactory( + testSSLContext.clientContext.getSocketFactory(), allSupportedProtocols), + disableFallbackScsv); connection.setSSLSocketFactory(clientSocketFactory); - assertEquals("This required a 2nd handshake", + assertEquals("This required fallbacks", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + // Confirm the server accepted a single connection. RecordedRequest retry = server.takeRequest(); assertEquals(0, retry.getSequenceNumber()); assertEquals("SSLv3", retry.getSslProtocol()); // Confirm the client fallback looks ok. List<SSLSocket> createdSockets = clientSocketFactory.getCreatedSockets(); - assertEquals(2, createdSockets.size()); - SSLSocket clientSocket1 = createdSockets.get(0); - List<String> clientSocket1EnabledProtocols = Arrays.asList( - clientSocket1.getEnabledProtocols()); - assertContains(clientSocket1EnabledProtocols, "TLSv1.2"); - List<String> clientSocket1EnabledCiphers = - Arrays.asList(clientSocket1.getEnabledCipherSuites()); - assertContainsNoneMatching( - clientSocket1EnabledCiphers, StandardNames.CIPHER_SUITE_FALLBACK); - - SSLSocket clientSocket2 = createdSockets.get(1); - List<String> clientSocket2EnabledProtocols = - Arrays.asList(clientSocket2.getEnabledProtocols()); - assertContainsNoneMatching(clientSocket2EnabledProtocols, "TLSv1.2"); - List<String> clientSocket2EnabledCiphers = - Arrays.asList(clientSocket2.getEnabledCipherSuites()); - assertContains(clientSocket2EnabledCiphers, StandardNames.CIPHER_SUITE_FALLBACK); + assertEquals(4, createdSockets.size()); + TlsFallbackDisabledScsvSSLSocket clientSocket1 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0); + assertSslSocket(clientSocket1, + false /* expectedWasFallbackScsvSet */, "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3"); + + TlsFallbackDisabledScsvSSLSocket clientSocket2 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(1); + assertSslSocket(clientSocket2, + true /* expectedWasFallbackScsvSet */, "TLSv1.1", "TLSv1", "SSLv3"); + + TlsFallbackDisabledScsvSSLSocket clientSocket3 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(2); + assertSslSocket(clientSocket3, true /* expectedWasFallbackScsvSet */, "TLSv1", "SSLv3"); + + TlsFallbackDisabledScsvSSLSocket clientSocket4 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(3); + assertSslSocket(clientSocket4, true /* expectedWasFallbackScsvSet */, "SSLv3"); + } + + public void testSslFallback_defaultProtocols() throws Exception { + TestSSLContext testSSLContext = TestSSLContext.create(); + + server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); + server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); + server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); + server.enqueue(new MockResponse().setBody("This required fallbacks")); + server.play(); + + HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); + // Keeps track of the client sockets created so that we can interrogate them. + final boolean disableFallbackScsv = true; + FallbackTestClientSocketFactory clientSocketFactory = new FallbackTestClientSocketFactory( + testSSLContext.clientContext.getSocketFactory(), + disableFallbackScsv); + connection.setSSLSocketFactory(clientSocketFactory); + assertEquals("This required fallbacks", + readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + + // Confirm the server accepted a single connection. + RecordedRequest retry = server.takeRequest(); + assertEquals(0, retry.getSequenceNumber()); + assertEquals("TLSv1", retry.getSslProtocol()); + + // Confirm the client fallback looks ok. + List<SSLSocket> createdSockets = clientSocketFactory.getCreatedSockets(); + assertEquals(3, createdSockets.size()); + TlsFallbackDisabledScsvSSLSocket clientSocket1 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0); + assertSslSocket(clientSocket1, + false /* expectedWasFallbackScsvSet */, "TLSv1.2", "TLSv1.1", "TLSv1"); + + TlsFallbackDisabledScsvSSLSocket clientSocket2 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(1); + assertSslSocket(clientSocket2, true /* expectedWasFallbackScsvSet */, "TLSv1.1", "TLSv1"); + + TlsFallbackDisabledScsvSSLSocket clientSocket3 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(2); + assertSslSocket(clientSocket3, true /* expectedWasFallbackScsvSet */, "TLSv1"); + } + + private static void assertSslSocket(TlsFallbackDisabledScsvSSLSocket socket, + boolean expectedWasFallbackScsvSet, String... expectedEnabledProtocols) { + Set<String> enabledProtocols = + new HashSet<String>(Arrays.asList(socket.getEnabledProtocols())); + Set<String> expectedProtocolsSet = new HashSet<String>(Arrays.asList(expectedEnabledProtocols)); + assertEquals(expectedProtocolsSet, enabledProtocols); + assertEquals(expectedWasFallbackScsvSet, socket.wasTlsFallbackScsvSet()); } public void testInspectSslBeforeConnect() throws Exception { @@ -2491,36 +2696,37 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) + public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - return delegate.createSocket(s, host, port, autoClose); + return (SSLSocket) delegate.createSocket(s, host, port, autoClose); } @Override - public Socket createSocket() throws IOException { - return delegate.createSocket(); + public SSLSocket createSocket() throws IOException { + return (SSLSocket) delegate.createSocket(); } @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - return delegate.createSocket(host, port); + public SSLSocket createSocket(String host, int port) + throws IOException, UnknownHostException { + return (SSLSocket) delegate.createSocket(host, port); } @Override - public Socket createSocket(String host, int port, InetAddress localHost, + public SSLSocket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { - return delegate.createSocket(host, port, localHost, localPort); + return (SSLSocket) delegate.createSocket(host, port, localHost, localPort); } @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - return delegate.createSocket(host, port); + public SSLSocket createSocket(InetAddress host, int port) throws IOException { + return (SSLSocket) delegate.createSocket(host, port); } @Override - public Socket createSocket(InetAddress address, int port, + public SSLSocket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return delegate.createSocket(address, port, localAddress, localPort); + return (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); } } @@ -2539,7 +2745,7 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) + public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose); socket.setEnabledProtocols(protocols); @@ -2547,21 +2753,22 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } @Override - public Socket createSocket() throws IOException { + public SSLSocket createSocket() throws IOException { SSLSocket socket = (SSLSocket) delegate.createSocket(); socket.setEnabledProtocols(protocols); return socket; } @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + public SSLSocket createSocket(String host, int port) + throws IOException, UnknownHostException { SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); socket.setEnabledProtocols(protocols); return socket; } @Override - public Socket createSocket(String host, int port, InetAddress localHost, + public SSLSocket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort); socket.setEnabledProtocols(protocols); @@ -2569,14 +2776,14 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } @Override - public Socket createSocket(InetAddress host, int port) throws IOException { + public SSLSocket createSocket(InetAddress host, int port) throws IOException { SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); socket.setEnabledProtocols(protocols); return socket; } @Override - public Socket createSocket(InetAddress address, int port, + public SSLSocket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { SSLSocket socket = (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); @@ -2586,58 +2793,337 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } /** - * An SSLSocketFactory that delegates calls and keeps a record of any sockets created. + * An {@link javax.net.ssl.SSLSocket} that delegates all calls. */ - private static class RecordingSocketFactory extends DelegatingSSLSocketFactory { + private static abstract class DelegatingSSLSocket extends SSLSocket { + protected final SSLSocket delegate; + + public DelegatingSSLSocket(SSLSocket delegate) { + this.delegate = delegate; + } + + @Override public void shutdownInput() throws IOException { + delegate.shutdownInput(); + } + + @Override public void shutdownOutput() throws IOException { + delegate.shutdownOutput(); + } + + @Override public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override public String[] getEnabledCipherSuites() { + return delegate.getEnabledCipherSuites(); + } + + @Override public void setEnabledCipherSuites(String[] suites) { + delegate.setEnabledCipherSuites(suites); + } + + @Override public String[] getSupportedProtocols() { + return delegate.getSupportedProtocols(); + } + + @Override public String[] getEnabledProtocols() { + return delegate.getEnabledProtocols(); + } + + @Override public void setEnabledProtocols(String[] protocols) { + delegate.setEnabledProtocols(protocols); + } + + @Override public SSLSession getSession() { + return delegate.getSession(); + } + + @Override public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { + delegate.addHandshakeCompletedListener(listener); + } + + @Override public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { + delegate.removeHandshakeCompletedListener(listener); + } + + @Override public void startHandshake() throws IOException { + delegate.startHandshake(); + } + + @Override public void setUseClientMode(boolean mode) { + delegate.setUseClientMode(mode); + } + + @Override public boolean getUseClientMode() { + return delegate.getUseClientMode(); + } + + @Override public void setNeedClientAuth(boolean need) { + delegate.setNeedClientAuth(need); + } + + @Override public void setWantClientAuth(boolean want) { + delegate.setWantClientAuth(want); + } + + @Override public boolean getNeedClientAuth() { + return delegate.getNeedClientAuth(); + } + + @Override public boolean getWantClientAuth() { + return delegate.getWantClientAuth(); + } + @Override public void setEnableSessionCreation(boolean flag) { + delegate.setEnableSessionCreation(flag); + } + + @Override public boolean getEnableSessionCreation() { + return delegate.getEnableSessionCreation(); + } + + @Override public SSLParameters getSSLParameters() { + return delegate.getSSLParameters(); + } + + @Override public void setSSLParameters(SSLParameters p) { + delegate.setSSLParameters(p); + } + + @Override public void close() throws IOException { + delegate.close(); + } + + @Override public InetAddress getInetAddress() { + return delegate.getInetAddress(); + } + + @Override public InputStream getInputStream() throws IOException { + return delegate.getInputStream(); + } + + @Override public boolean getKeepAlive() throws SocketException { + return delegate.getKeepAlive(); + } + + @Override public InetAddress getLocalAddress() { + return delegate.getLocalAddress(); + } + + @Override public int getLocalPort() { + return delegate.getLocalPort(); + } + + @Override public OutputStream getOutputStream() throws IOException { + return delegate.getOutputStream(); + } + + @Override public int getPort() { + return delegate.getPort(); + } + + @Override public int getSoLinger() throws SocketException { + return delegate.getSoLinger(); + } + + @Override public int getReceiveBufferSize() throws SocketException { + return delegate.getReceiveBufferSize(); + } + + @Override public int getSendBufferSize() throws SocketException { + return delegate.getSendBufferSize(); + } + + @Override public int getSoTimeout() throws SocketException { + return delegate.getSoTimeout(); + } + + @Override public boolean getTcpNoDelay() throws SocketException { + return delegate.getTcpNoDelay(); + } + + @Override public void setKeepAlive(boolean keepAlive) throws SocketException { + delegate.setKeepAlive(keepAlive); + } + + @Override public void setSendBufferSize(int size) throws SocketException { + delegate.setSendBufferSize(size); + } + + @Override public void setReceiveBufferSize(int size) throws SocketException { + delegate.setReceiveBufferSize(size); + } + + @Override public void setSoLinger(boolean on, int timeout) throws SocketException { + delegate.setSoLinger(on, timeout); + } + + @Override public void setSoTimeout(int timeout) throws SocketException { + delegate.setSoTimeout(timeout); + } + + @Override public void setTcpNoDelay(boolean on) throws SocketException { + delegate.setTcpNoDelay(on); + } + + @Override public String toString() { + return delegate.toString(); + } + + @Override public SocketAddress getLocalSocketAddress() { + return delegate.getLocalSocketAddress(); + } + + @Override public SocketAddress getRemoteSocketAddress() { + return delegate.getRemoteSocketAddress(); + } + + @Override public boolean isBound() { + return delegate.isBound(); + } + + @Override public boolean isConnected() { + return delegate.isConnected(); + } + + @Override public boolean isClosed() { + return delegate.isClosed(); + } + + @Override public void bind(SocketAddress localAddr) throws IOException { + delegate.bind(localAddr); + } + + @Override public void connect(SocketAddress remoteAddr) throws IOException { + delegate.connect(remoteAddr); + } + + @Override public void connect(SocketAddress remoteAddr, int timeout) throws IOException { + delegate.connect(remoteAddr, timeout); + } + + @Override public boolean isInputShutdown() { + return delegate.isInputShutdown(); + } + + @Override public boolean isOutputShutdown() { + return delegate.isOutputShutdown(); + } + + @Override public void setReuseAddress(boolean reuse) throws SocketException { + delegate.setReuseAddress(reuse); + } + + @Override public boolean getReuseAddress() throws SocketException { + return delegate.getReuseAddress(); + } + + @Override public void setOOBInline(boolean oobinline) throws SocketException { + delegate.setOOBInline(oobinline); + } + + @Override public boolean getOOBInline() throws SocketException { + return delegate.getOOBInline(); + } + + @Override public void setTrafficClass(int value) throws SocketException { + delegate.setTrafficClass(value); + } + + @Override public int getTrafficClass() throws SocketException { + return delegate.getTrafficClass(); + } + + @Override public void sendUrgentData(int value) throws IOException { + delegate.sendUrgentData(value); + } + + @Override public SocketChannel getChannel() { + return delegate.getChannel(); + } + + @Override public void setPerformancePreferences(int connectionTime, int latency, + int bandwidth) { + delegate.setPerformancePreferences(connectionTime, latency, bandwidth); + } + } + + /** + * An SSLSocketFactory that delegates calls. It keeps a record of any sockets created. + * If {@link #disableTlsFallbackScsv} is set to {@code true} then sockets created by the + * delegate are wrapped with ones that will not accept the {@link #TLS_FALLBACK_SCSV} cipher, + * thus bypassing server-side fallback checks on platforms that support it. Unfortunately this + * wrapping will disable any reflection-based calls to SSLSocket from Platform. + */ + private static class FallbackTestClientSocketFactory extends DelegatingSSLSocketFactory { + /** + * The cipher suite used during TLS connection fallback to indicate a fallback. + * See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 + */ + public static final String TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"; + + private final boolean disableTlsFallbackScsv; private final List<SSLSocket> createdSockets = new ArrayList<SSLSocket>(); - private RecordingSocketFactory(SSLSocketFactory delegate) { + public FallbackTestClientSocketFactory(SSLSocketFactory delegate, + boolean disableTlsFallbackScsv) { super(delegate); + this.disableTlsFallbackScsv = disableTlsFallbackScsv; } - @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) + @Override public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose); + SSLSocket socket = super.createSocket(s, host, port, autoClose); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket() throws IOException { - SSLSocket socket = (SSLSocket) delegate.createSocket(); + @Override public SSLSocket createSocket() throws IOException { + SSLSocket socket = super.createSocket(); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); + @Override public SSLSocket createSocket(String host,int port) throws IOException { + SSLSocket socket = super.createSocket(host, port); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket(String host, int port, InetAddress localHost, - int localPort) throws IOException, UnknownHostException { - SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort); + @Override public SSLSocket createSocket(String host,int port, InetAddress localHost, + int localPort) throws IOException { + SSLSocket socket = super.createSocket(host, port, localHost, localPort); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); + @Override public SSLSocket createSocket(InetAddress host,int port) throws IOException { + SSLSocket socket = super.createSocket(host, port); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket(InetAddress address, int port, + @Override public SSLSocket createSocket(InetAddress address,int port, InetAddress localAddress, int localPort) throws IOException { - SSLSocket socket = - (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); + SSLSocket socket = super.createSocket(address, port, localAddress, localPort); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } @@ -2647,4 +3133,31 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } } + private static class TlsFallbackDisabledScsvSSLSocket extends DelegatingSSLSocket { + + private boolean tlsFallbackScsvSet; + + public TlsFallbackDisabledScsvSSLSocket(SSLSocket socket) { + super(socket); + } + + @Override public void setEnabledCipherSuites(String[] suites) { + List<String> enabledCipherSuites = new ArrayList<String>(suites.length); + for (String suite : suites) { + if (suite.equals(FallbackTestClientSocketFactory.TLS_FALLBACK_SCSV)) { + // Record that an attempt was made to set TLS_FALLBACK_SCSV, but don't actually + // set it. + tlsFallbackScsvSet = true; + } else { + enabledCipherSuites.add(suite); + } + } + delegate.setEnabledCipherSuites( + enabledCipherSuites.toArray(new String[enabledCipherSuites.size()])); + } + + public boolean wasTlsFallbackScsvSet() { + return tlsFallbackScsvSet; + } + } } diff --git a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java index 9789197..41b434d 100644 --- a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java @@ -15,7 +15,6 @@ */ package libcore.java.nio.channels; -import android.system.OsConstants; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; @@ -28,7 +27,6 @@ import java.nio.channels.SocketChannel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; -import libcore.io.Libcore; import tests.net.StuckServer; public class SelectorTest extends TestCase { @@ -71,25 +69,6 @@ public class SelectorTest extends TestCase { } } - // http://b/6453247 - // This test won't work on the host until/unless we start using libcorkscrew there. - // The runtime itself blocks SIGQUIT, so that doesn't cause poll(2) to EINTR directly. - // The EINTR is caused by the way libcorkscrew works. - public void testEINTR() throws Exception { - Selector selector = Selector.open(); - new Thread(new Runnable() { - @Override public void run() { - try { - Thread.sleep(2000); - Libcore.os.kill(Libcore.os.getpid(), OsConstants.SIGQUIT); - } catch (Exception ex) { - fail(); - } - } - }).start(); - assertEquals(0, selector.select()); - } - // http://code.google.com/p/android/issues/detail?id=15388 public void testInterrupted() throws IOException { Selector selector = Selector.open(); diff --git a/luni/src/test/java/libcore/java/nio/charset/CharsetEncoderTest.java b/luni/src/test/java/libcore/java/nio/charset/CharsetEncoderTest.java index ff510e0..e9ab8ae 100644 --- a/luni/src/test/java/libcore/java/nio/charset/CharsetEncoderTest.java +++ b/luni/src/test/java/libcore/java/nio/charset/CharsetEncoderTest.java @@ -20,8 +20,10 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; +import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; import java.util.Arrays; public class CharsetEncoderTest extends junit.framework.TestCase { @@ -161,4 +163,71 @@ public class CharsetEncoderTest extends junit.framework.TestCase { assertEquals(CoderResult.UNDERFLOW, cr); assertEquals(8, bb.position()); } + + // Discards all input. Outputs a single byte 'X' on flush. + private static final class MockCharset extends Charset { + static final Charset INSTANCE = new MockCharset(); + + private MockCharset() { + super("MockCharset", new String[0]); + } + + public boolean contains(Charset charset) { + return false; + } + + public CharsetEncoder newEncoder() { + return new CharsetEncoder(INSTANCE, 1.f, 1.f) { + protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) { + in.position(in.limit()); + return CoderResult.UNDERFLOW; + } + + protected CoderResult implFlush(ByteBuffer out) { + out.put((byte) 'X'); + return CoderResult.UNDERFLOW; + } + }; + } + + public CharsetDecoder newDecoder() { + return new CharsetDecoder(INSTANCE, 1.f, 1.f) { + protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { + in.position(in.limit()); + return CoderResult.UNDERFLOW; + } + }; + } + } + + // Repeated calls to flush() should not result in repeated calls to implFlush(). + public void testFlushNotCallingImplFlushRepeatedly() { + CharsetEncoder e = MockCharset.INSTANCE.newEncoder(); + ByteBuffer bb = ByteBuffer.allocate(4); + CoderResult cr = e.encode(CharBuffer.allocate(0), bb, true); + assertEquals(CoderResult.UNDERFLOW, cr); + cr = e.flush(bb); + assertEquals(CoderResult.UNDERFLOW, cr); + cr = e.flush(bb); + assertEquals(CoderResult.UNDERFLOW, cr); + assertEquals(1, bb.position()); + assertEquals((byte) 'X', bb.get(0)); + assertEquals(0x00, bb.get(1)); + assertEquals(0x00, bb.get(2)); + assertEquals(0x00, bb.get(3)); + } + + // http://b/19185235 + public void testFlushWithIncompleteInput() { + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + ByteBuffer output = ByteBuffer.allocate(10); + CoderResult result = encoder.encode(CharBuffer.wrap("\ud800"), output, + true /* endOfInput */); + assertTrue(result.isUnderflow()); + + result = encoder.flush(output); + assertTrue(result.isMalformed()); + assertEquals(1, result.length()); + assertEquals(0, output.position()); + } } diff --git a/luni/src/test/java/libcore/java/nio/charset/SettableCharsetProvider.java b/luni/src/test/java/libcore/java/nio/charset/SettableCharsetProvider.java new file mode 100644 index 0000000..b4886d2 --- /dev/null +++ b/luni/src/test/java/libcore/java/nio/charset/SettableCharsetProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package libcore.java.nio.charset; + +import java.nio.charset.Charset; +import java.nio.charset.spi.CharsetProvider; +import java.util.Collections; +import java.util.Iterator; + +/** + * This class is registered as a charset provider by the META-INF in the libcore + * tests jar. Since there isn't any convenient API to dynamically register and de-register + * charset-providers, this class allows tests to plug in a delegate that lives for the + * duration of the test. + */ +public final class SettableCharsetProvider extends CharsetProvider { + private static CharsetProvider delegate; + + public static void setDelegate(CharsetProvider cp) { + delegate = cp; + } + + public static void clearDelegate() { + delegate = null; + } + + @Override + public Iterator<Charset> charsets() { + if (delegate != null) { + return delegate.charsets(); + } + + return Collections.emptyIterator(); + } + + @Override + public Charset charsetForName(String charsetName) { + if (delegate != null) { + return delegate.charsetForName(charsetName); + } + + return null; + } +} diff --git a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java index e7fdb1f..7e08b5f 100644 --- a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java +++ b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java @@ -121,7 +121,10 @@ public class KeyPairGeneratorTest extends TestCase { } String algorithm = service.getAlgorithm(); - // AndroidKeyStore is tested in CTS. + // Do not test AndroidKeyStore's KeyPairGenerator. It cannot be initialized without + // providing AndroidKeyStore-specific algorithm parameters. + // It's OKish not to test AndroidKeyStore's KeyPairGenerator here because it's tested + // by cts/tests/test/keystore. if ("AndroidKeyStore".equals(provider.getName())) { continue; } diff --git a/luni/src/test/java/libcore/java/security/ProviderTest.java b/luni/src/test/java/libcore/java/security/ProviderTest.java index 994214b..0be558e 100644 --- a/luni/src/test/java/libcore/java/security/ProviderTest.java +++ b/luni/src/test/java/libcore/java/security/ProviderTest.java @@ -160,7 +160,7 @@ public class ProviderTest extends TestCase { public void test_Provider_Properties() throws Exception { /* * A useful reference on Provider properties - * <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/crypto/HowToImplAProvider.html> + * <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/crypto/HowToImplAProvider.html"> * How to Implement a Provider in the Java ™ Cryptography Architecture * </a> */ diff --git a/luni/src/test/java/libcore/java/security/SignatureTest.java b/luni/src/test/java/libcore/java/security/SignatureTest.java index 5e02f10..e546f4f 100644 --- a/luni/src/test/java/libcore/java/security/SignatureTest.java +++ b/luni/src/test/java/libcore/java/security/SignatureTest.java @@ -71,6 +71,24 @@ public class SignatureTest extends TestCase { } } + public void testSignature_getInstance_DoesNotSupportKeyClass_Success() throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Signature.FOO", MockSignatureSpi.AllKeyTypes.class.getName()); + put("Signature.FOO SupportedKeyClasses", "None"); + } + }; + + Security.addProvider(mockProvider); + try { + Signature s = Signature.getInstance("FOO", mockProvider); + s.initSign(new MockPrivateKey()); + assertEquals(mockProvider, s.getProvider()); + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + public void testSignature_getInstance_OnlyUsesSpecifiedProvider_SameNameAndClass_Success() throws Exception { Provider mockProvider = new MockProvider("MockProvider") { diff --git a/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java b/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java index e2f21e8..a3a721a 100644 --- a/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java +++ b/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java @@ -16,11 +16,13 @@ package libcore.java.security.cert; +import com.android.org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import com.android.org.bouncycastle.asn1.x509.BasicConstraints; -import com.android.org.bouncycastle.asn1.x509.X509Extensions; +import com.android.org.bouncycastle.asn1.x509.Extension; +import com.android.org.bouncycastle.asn1.x509.SubjectKeyIdentifier; +import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; import com.android.org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; -import com.android.org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -28,13 +30,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.OptionalDataException; -import java.io.StreamCorruptedException; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; +import java.security.PublicKey; import java.security.Security; import java.security.cert.CertPath; import java.security.cert.Certificate; @@ -554,25 +557,26 @@ public class CertificateFactoryTest extends TestCase { X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); + PublicKey pubKey = keyPair.getPublic(); certGen.setSerialNumber(serial); certGen.setIssuerDN(issuerPrincipal); certGen.setNotBefore(startDate); certGen.setNotAfter(expiryDate); certGen.setSubjectDN(subjectPrincipal); - certGen.setPublicKey(keyPair.getPublic()); + certGen.setPublicKey(pubKey); certGen.setSignatureAlgorithm("SHA1withRSA"); if (issuer != null) { - certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, + certGen.addExtension(Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(issuer.certificate)); } else { - certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, - new AuthorityKeyIdentifierStructure(keyPair.getPublic())); + certGen.addExtension(Extension.authorityKeyIdentifier, false, + new AuthorityKeyIdentifier(generatePublicKeyDigest(pubKey))); } - certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, - new SubjectKeyIdentifierStructure(keyPair.getPublic())); - certGen.addExtension(X509Extensions.BasicConstraints, true, basicConstraints); + certGen.addExtension(Extension.subjectKeyIdentifier, false, + new SubjectKeyIdentifier(generatePublicKeyDigest(pubKey))); + certGen.addExtension(Extension.basicConstraints, true, basicConstraints); X509Certificate cert = certGen.generate(caKey); @@ -582,4 +586,18 @@ public class CertificateFactoryTest extends TestCase { return holder; } + + /** + * Generates a type 1 key identifier according to RFC 3280 4.2.1.2. + */ + private static byte[] generatePublicKeyDigest(PublicKey pubKey) { + SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()); + MessageDigest sha1digest; + try { + sha1digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-1 not available"); + } + return sha1digest.digest(spki.getPublicKeyData().getBytes()); + } } diff --git a/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java b/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java index 42de50a..1611120 100644 --- a/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java +++ b/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java @@ -256,15 +256,7 @@ public class X509CRLTest extends TestCase { private void getSigAlgName(CertificateFactory f) throws Exception { X509CRL crlRsa = getCRL(f, CRL_RSA); - - String actual = crlRsa.getSigAlgName().toUpperCase(Locale.US); - - // Bouncycastle is broken - if ("BC".equals(f.getProvider().getName())) { - assertEquals("1.2.840.113549.1.1.5", actual); - } else { - assertEquals("SHA1WITHRSA", actual); - } + assertEquals("SHA1WITHRSA", getCRL(f, CRL_RSA).getSigAlgName().toUpperCase(Locale.ROOT)); } private void getSigAlgOID(CertificateFactory f) throws Exception { diff --git a/luni/src/test/java/libcore/java/sql/TimestampTest.java b/luni/src/test/java/libcore/java/sql/TimestampTest.java index 2985848..71ac8c8 100644 --- a/luni/src/test/java/libcore/java/sql/TimestampTest.java +++ b/luni/src/test/java/libcore/java/sql/TimestampTest.java @@ -144,4 +144,12 @@ public final class TimestampTest extends TestCase { } catch (IllegalArgumentException expected) { } } + // http://b/19756610 + public void testAsymmetricEquals() { + Timestamp timestamp = new Timestamp(0); + java.util.Date date = new java.util.Date(0); + + assertTrue(date.equals(timestamp)); + assertFalse(timestamp.equals(date)); + } } diff --git a/luni/src/test/java/libcore/java/text/BreakIteratorTest.java b/luni/src/test/java/libcore/java/text/BreakIteratorTest.java index 47701c8..de2ae52 100644 --- a/luni/src/test/java/libcore/java/text/BreakIteratorTest.java +++ b/luni/src/test/java/libcore/java/text/BreakIteratorTest.java @@ -172,32 +172,4 @@ public class BreakIteratorTest extends junit.framework.TestCase { // Expected exception } } - - // http://code.google.com/p/android/issues/detail?id=41143 - // This code is inherently unsafe and crazy; - // we're just trying to provoke native crashes! - public void testConcurrentBreakIteratorAccess() throws Exception { - final BreakIterator it = BreakIterator.getCharacterInstance(); - - ArrayList<Thread> threads = new ArrayList<Thread>(); - for (int i = 0; i < 10; ++i) { - Thread t = new Thread(new Runnable() { - public void run() { - for (int i = 0; i < 4096; ++i) { - it.setText("some example text"); - for (int index = it.first(); index != BreakIterator.DONE; index = it.next()) { - } - } - } - }); - threads.add(t); - } - - for (Thread t : threads) { - t.start(); - } - for (Thread t : threads) { - t.join(); - } - } } diff --git a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java index e6933e6..0c97f34 100644 --- a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java +++ b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java @@ -147,17 +147,17 @@ public class DateFormatSymbolsTest extends junit.framework.TestCase { } // http://b/7955614 - public void test_getZoneStrings_GMT_short_names() throws Exception { + public void test_getZoneStrings_Apia() throws Exception { String[][] array = DateFormatSymbols.getInstance(Locale.US).getZoneStrings(); for (int i = 0; i < array.length; ++i) { String[] row = array[i]; - // America/Santiago is somewhat arbitrary; we just want a zone we have to generate + // Pacific/Apia is somewhat arbitrary; we just want a zone we have to generate // "GMT" strings for the short names. - if (row[0].equals("America/Santiago")) { - assertEquals("Chile Standard Time", row[1]); - assertEquals("GMT-03:00", row[2]); - assertEquals("Chile Summer Time", row[3]); - assertEquals("GMT-03:00", row[4]); + if (row[0].equals("Pacific/Apia")) { + assertEquals("Apia Standard Time", row[1]); + assertEquals("GMT+13:00", row[2]); + assertEquals("Apia Daylight Time", row[3]); + assertEquals("GMT+14:00", row[4]); } } } diff --git a/luni/src/test/java/libcore/java/text/NumberFormatTest.java b/luni/src/test/java/libcore/java/text/NumberFormatTest.java index 0678e96..87fe96d 100644 --- a/luni/src/test/java/libcore/java/text/NumberFormatTest.java +++ b/luni/src/test/java/libcore/java/text/NumberFormatTest.java @@ -17,6 +17,7 @@ package libcore.java.text; import java.math.BigInteger; +import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.FieldPosition; @@ -93,6 +94,21 @@ public class NumberFormatTest extends junit.framework.TestCase { } } + public void testPercentageRounding() throws Exception { + NumberFormat nf = NumberFormat.getPercentInstance(Locale.US); + assertEquals("15%", nf.format(0.149)); + assertEquals("14%", nf.format(0.142)); + + nf.setRoundingMode(RoundingMode.UP); + assertEquals("15%", nf.format(0.142)); + + nf.setRoundingMode(RoundingMode.DOWN); + assertEquals("14%", nf.format(0.149)); + + nf.setMaximumFractionDigits(1); + assertEquals("14.9%", nf.format(0.149)); + } + // https://code.google.com/p/android/issues/detail?id=62269 public void test_62269() throws Exception { NumberFormat nf = NumberFormat.getNumberInstance(Locale.US); diff --git a/luni/src/test/java/libcore/java/util/CalendarTest.java b/luni/src/test/java/libcore/java/util/CalendarTest.java index e0e1a35..2e13ad8 100644 --- a/luni/src/test/java/libcore/java/util/CalendarTest.java +++ b/luni/src/test/java/libcore/java/util/CalendarTest.java @@ -263,4 +263,76 @@ public class CalendarTest extends junit.framework.TestCase { b.setTime(d); assertEquals(a, b); } + + public void testCloneMakesDeepCopyOfCalendarFields() { + FakeCalendar c = new FakeCalendar(); + FakeCalendar c2 = (FakeCalendar) c.clone(); + + assertFalse(c.getTimeZone() == c2.getTimeZone()); + assertEquals(c.getTimeZone(), c2.getTimeZone()); + + // The default clone() implementation makes a deep copy of calendar + // fields... + assertFalse(c.getCalenderFields() == c2.getCalenderFields()); + // ,,, and a shallow copy of subclass fields. + assertSame(c.getSubclassFields(), c2.getSubclassFields()); + } + + public static class FakeCalendar extends Calendar { + + private int[] subclassFields; + + public FakeCalendar() { + super(TimeZone.getDefault(), Locale.getDefault()); + subclassFields = new int[12]; + } + + public int[] getCalenderFields() { + return fields; + } + + public int[] getSubclassFields() { + return subclassFields; + } + + @Override + public void add(int field, int value) { + + } + + @Override + protected void computeFields() { + + } + + @Override + protected void computeTime() { + + } + + @Override + public int getGreatestMinimum(int field) { + return 0; + } + + @Override + public int getLeastMaximum(int field) { + return 0; + } + + @Override + public int getMaximum(int field) { + return 0; + } + + @Override + public int getMinimum(int field) { + return 0; + } + + @Override + public void roll(int field, boolean increment) { + + } + } } diff --git a/luni/src/test/java/libcore/java/util/CollectionsTest.java b/luni/src/test/java/libcore/java/util/CollectionsTest.java index 80c769e..bc73817 100644 --- a/luni/src/test/java/libcore/java/util/CollectionsTest.java +++ b/luni/src/test/java/libcore/java/util/CollectionsTest.java @@ -17,7 +17,10 @@ package libcore.java.util; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; import java.util.Enumeration; import java.util.Iterator; import java.util.ListIterator; @@ -94,4 +97,92 @@ public final class CollectionsTest extends TestCase { } catch (IllegalStateException expected) { } } + + public void testSortFastPath_incrementsModcount() { + ArrayList<String> list = new ArrayList<String>(16); + list.add("coven"); + list.add("asylum"); + list.add("murder house"); + list.add("freak show"); + + Iterator<String> it = list.iterator(); + it.next(); + Collections.sort(list); + try { + it.next(); + fail(); + } catch (ConcurrentModificationException expected) { + } + } + + /** + * A value type whose {@code compareTo} method returns one of {@code 0}, + * {@code Integer.MIN_VALUE} and {@code Integer.MAX_VALUE}. + */ + static final class IntegerWithExtremeComparator + implements Comparable<IntegerWithExtremeComparator> { + private final int value; + + public IntegerWithExtremeComparator(int value) { + this.value = value; + } + + @Override + public int compareTo(IntegerWithExtremeComparator another) { + if (another.value == this.value) { + return 0; + } else if (another.value > this.value) { + return Integer.MIN_VALUE; + } else { + return Integer.MAX_VALUE; + } + } + } + + // http://b/19749094 + public void testBinarySearch_comparatorThatReturnsMinAndMaxValue() { + ArrayList<Integer> list = new ArrayList<Integer>(16); + list.add(4); + list.add(9); + list.add(11); + list.add(14); + list.add(16); + + int index = Collections.binarySearch(list, 9, new Comparator<Integer>() { + @Override + public int compare(Integer lhs, Integer rhs) { + final int compare = lhs.compareTo(rhs); + if (compare == 0) { + return 0; + } else if (compare < 0) { + return Integer.MIN_VALUE; + } else { + return Integer.MAX_VALUE; + } + } + }); + assertEquals(1, index); + + ArrayList<IntegerWithExtremeComparator> list2 = + new ArrayList<IntegerWithExtremeComparator>(); + list2.add(new IntegerWithExtremeComparator(4)); + list2.add(new IntegerWithExtremeComparator(9)); + list2.add(new IntegerWithExtremeComparator(11)); + list2.add(new IntegerWithExtremeComparator(14)); + list2.add(new IntegerWithExtremeComparator(16)); + + assertEquals(1, Collections.binarySearch(list2, new IntegerWithExtremeComparator(9))); + } + + public void testBinarySearch_emptyCollection() { + assertEquals(-1, Collections.binarySearch(new ArrayList<Integer>(), 9)); + + assertEquals(-1, Collections.binarySearch(new ArrayList<Integer>(), 9, + new Comparator<Integer>() { + @Override + public int compare(Integer lhs, Integer rhs) { + return lhs.compareTo(rhs); + } + })); + } } diff --git a/luni/src/test/java/libcore/java/util/CurrencyTest.java b/luni/src/test/java/libcore/java/util/CurrencyTest.java index cf2a1b6..e4c56e5 100644 --- a/luni/src/test/java/libcore/java/util/CurrencyTest.java +++ b/luni/src/test/java/libcore/java/util/CurrencyTest.java @@ -58,7 +58,7 @@ public class CurrencyTest extends junit.framework.TestCase { assertEquals("Swiss Franc", Currency.getInstance("CHF").getDisplayName(Locale.US)); assertEquals("Schweizer Franken", Currency.getInstance("CHF").getDisplayName(new Locale("de", "CH"))); assertEquals("franc suisse", Currency.getInstance("CHF").getDisplayName(new Locale("fr", "CH"))); - assertEquals("Franco Svizzero", Currency.getInstance("CHF").getDisplayName(new Locale("it", "CH"))); + assertEquals("franco svizzero", Currency.getInstance("CHF").getDisplayName(new Locale("it", "CH"))); } public void test_getDefaultFractionDigits() throws Exception { diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java index c72ecd7..e1e84ab 100644 --- a/luni/src/test/java/libcore/java/util/LocaleTest.java +++ b/luni/src/test/java/libcore/java/util/LocaleTest.java @@ -20,6 +20,7 @@ import java.text.BreakIterator; import java.text.Collator; import java.text.DateFormat; import java.text.DateFormatSymbols; +import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.Calendar; import java.util.IllformedLocaleException; @@ -1146,4 +1147,60 @@ public class LocaleTest extends junit.framework.TestCase { assertEquals("variant", locale.getVariant()); assertEquals(locale, Locale.forLanguageTag(locale.toLanguageTag())); } + + public void testArabicDigits() throws Exception { + // ar-DZ uses latn digits by default, but we can override that. + Locale ar_DZ = Locale.forLanguageTag("ar-DZ"); + Locale ar_DZ_arab = Locale.forLanguageTag("ar-DZ-u-nu-arab"); + Locale ar_DZ_latn = Locale.forLanguageTag("ar-DZ-u-nu-latn"); + assertEquals('0', new DecimalFormatSymbols(ar_DZ).getZeroDigit()); + assertEquals('\u0660', new DecimalFormatSymbols(ar_DZ_arab).getZeroDigit()); + assertEquals('0', new DecimalFormatSymbols(ar_DZ_latn).getZeroDigit()); + + // ar-EG uses arab digits by default, but we can override that. + Locale ar_EG = Locale.forLanguageTag("ar-EG"); + Locale ar_EG_arab = Locale.forLanguageTag("ar-EG-u-nu-arab"); + Locale ar_EG_latn = Locale.forLanguageTag("ar-EG-u-nu-latn"); + assertEquals('\u0660', new DecimalFormatSymbols(ar_EG).getZeroDigit()); + assertEquals('\u0660', new DecimalFormatSymbols(ar_EG_arab).getZeroDigit()); + assertEquals('0', new DecimalFormatSymbols(ar_EG_latn).getZeroDigit()); + } + + public void testDefaultLocale() throws Exception { + final String userLanguage = System.getProperty("user.language", ""); + final String userRegion = System.getProperty("user.region", ""); + final String userLocale = System.getProperty("user.locale", ""); + try { + // Assert that user.locale gets priority. + System.setUnchangeableSystemProperty("user.locale", "de-DE"); + System.setUnchangeableSystemProperty("user.language", "en"); + System.setUnchangeableSystemProperty("user.region", "US"); + + Locale l = Locale.getDefaultLocaleFromSystemProperties(); + assertEquals("de", l.getLanguage()); + assertEquals("DE", l.getCountry()); + + // Assert that it's parsed as a full language tag. + System.setUnchangeableSystemProperty("user.locale", "de-Latn-DE"); + System.setUnchangeableSystemProperty("user.language", "en"); + System.setUnchangeableSystemProperty("user.region", "US"); + + l = Locale.getDefaultLocaleFromSystemProperties(); + assertEquals("de", l.getLanguage()); + assertEquals("DE", l.getCountry()); + assertEquals("Latn", l.getScript()); + + // Assert that we use "und" if we're faced with a bad language tag, and + // that we don't end up with a null default locale or an exception. + System.setUnchangeableSystemProperty("user.locale", "dexx-Latn-DE"); + + l = Locale.getDefaultLocaleFromSystemProperties(); + assertEquals("und", l.getLanguage()); + assertEquals("DE", l.getCountry()); + } finally { + System.setUnchangeableSystemProperty("user.language", userLanguage); + System.setUnchangeableSystemProperty("user.region", userRegion); + System.setUnchangeableSystemProperty("user.locale", userLocale); + } + } } diff --git a/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java b/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java index ecf2e5f..7a5fc4a 100644 --- a/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java +++ b/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java @@ -108,7 +108,7 @@ public class OldTimeZoneTest extends TestCase { TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); assertEquals("Pacific Daylight Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US)); assertEquals("Pacific Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.UK)); - assertEquals("heure avanc\u00e9e du Pacifique", + assertEquals("heure d’été du Pacifique", tz.getDisplayName(true, TimeZone.LONG, Locale.FRANCE)); assertEquals("heure normale du Pacifique nord-américain", tz.getDisplayName(false, TimeZone.LONG, Locale.FRANCE)); @@ -123,18 +123,6 @@ public class OldTimeZoneTest extends TestCase { assertEquals("GMT-07:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.FRANCE)); assertEquals("GMT-08:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.UK)); assertEquals("GMT-07:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.UK)); - - // The RI behavior mentioned above does not appear to be because "PST" is a legacy - // three-character timezone supported by the RI: it happens for "Asia/Tehran"/"IRST" too - // (IRST is not a legacy code). The RI may just use a different dataset that has "PST" / - // "IRST" as valid translations (even for scripts like Chinese). - TimeZone iranTz = TimeZone.getTimeZone("Asia/Tehran"); - assertEquals("Iran Summer Time", iranTz.getDisplayName(true, TimeZone.LONG, Locale.UK)); - assertEquals("Iran Daylight Time", iranTz.getDisplayName(true, TimeZone.LONG, Locale.US)); - assertEquals("Iran Standard Time", iranTz.getDisplayName(false, TimeZone.LONG, Locale.UK)); - assertEquals("Iran Standard Time", iranTz.getDisplayName(false, TimeZone.LONG, Locale.US)); - assertEquals("GMT+03:30", iranTz.getDisplayName(false, TimeZone.SHORT, Locale.UK)); - assertEquals("GMT+04:30", iranTz.getDisplayName(true, TimeZone.SHORT, Locale.UK)); } public void test_getID() { diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java index 1ca950c..68e9109 100644 --- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java +++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java @@ -73,6 +73,12 @@ public class TimeZoneTest extends TestCase { assertFalse(tz.inDaylightTime(date)); } + public void testGetDisplayNameShort_nonHourOffsets() { + TimeZone iranTz = TimeZone.getTimeZone("Asia/Tehran"); + assertEquals("GMT+03:30", iranTz.getDisplayName(false, TimeZone.SHORT, Locale.UK)); + assertEquals("GMT+04:30", iranTz.getDisplayName(true, TimeZone.SHORT, Locale.UK)); + } + public void testPreHistoricOffsets() throws Exception { // "Africa/Bissau" has just a few transitions and hasn't changed in a long time. // 1912-01-01 00:02:19-0100 ... 1912-01-01 00:02:20-0100 @@ -256,12 +262,12 @@ public class TimeZoneTest extends TestCase { } // http://b/7955614 - public void test_getDisplayName_GMT_short_names() throws Exception { - TimeZone tz = TimeZone.getTimeZone("America/Santiago"); - assertEquals("Chile Summer Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US)); - assertEquals("Chile Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US)); - assertEquals("GMT-03:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US)); - assertEquals("GMT-03:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US)); + public void testApia() throws Exception { + TimeZone tz = TimeZone.getTimeZone("Pacific/Apia"); + assertEquals("Apia Daylight Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US)); + assertEquals("Apia Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US)); + assertEquals("GMT+14:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US)); + assertEquals("GMT+13:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US)); } private static boolean isGmtString(String s) { @@ -290,4 +296,32 @@ public class TimeZoneTest extends TestCase { } } } + + // http://b/18839557 + public void testOverflowing32BitUnixDates() { + final TimeZone tz = TimeZone.getTimeZone("America/New_York"); + + // This timezone didn't have any daylight savings prior to 1917 and this + // date is sometime in 1901. + assertFalse(tz.inDaylightTime(new Date(-2206292400000L))); + assertEquals(-18000000, tz.getOffset(-2206292400000L)); + + // Nov 30th 2039, no daylight savings as per current rules. + assertFalse(tz.inDaylightTime(new Date(2206292400000L))); + assertEquals(-18000000, tz.getOffset(2206292400000L)); + } + + public void testTimeZoneIDLocalization() { + Locale defaultLocale = Locale.getDefault(); + try { + Locale.setDefault(new Locale("en")); + TimeZone en_timezone = TimeZone.getTimeZone("GMT+09:00"); + Locale.setDefault(new Locale("ar")); + TimeZone ar_timezone = TimeZone.getTimeZone("GMT+09:00"); + + assertEquals(en_timezone.getID(), ar_timezone.getID()); + } finally { + Locale.setDefault(defaultLocale); + } + } } diff --git a/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java b/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java index e5a6cd8..9496ad0 100644 --- a/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java +++ b/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java @@ -169,6 +169,10 @@ public class StrictJarFileTest extends TestCase { assertThrowsOnInit("Modified_SF_EntryAttributes.jar"); } + public void testJarSigning_removedEntry() throws Exception { + assertThrowsOnInit("removed.jar"); + } + private void assertThrowsOnInit(String name) throws Exception { Support_Resources.copyFile(resources, null, name); try { diff --git a/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java b/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java new file mode 100644 index 0000000..9e049c0 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.util.zip; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import junit.framework.TestCase; + +import tests.support.resource.Support_Resources; + +public abstract class AbstractZipFileTest extends TestCase { + /** + * Exercise Inflater's ability to refill the zlib's input buffer. As of this + * writing, this buffer's max size is 64KiB compressed bytes. We'll write a + * full megabyte of uncompressed data, which should be sufficient to exhaust + * the buffer. http://b/issue?id=2734751 + */ + public void testInflatingFilesRequiringZipRefill() throws IOException { + int originalSize = 1024 * 1024; + byte[] readBuffer = new byte[8192]; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */); + ZipFile zipFile = new ZipFile(f); + for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { + ZipEntry zipEntry = e.nextElement(); + assertTrue("This test needs >64 KiB of compressed data to exercise Inflater", + zipEntry.getCompressedSize() > (64 * 1024)); + InputStream is = zipFile.getInputStream(zipEntry); + while (is.read(readBuffer, 0, readBuffer.length) != -1) {} + is.close(); + } + zipFile.close(); + } + + private static void replaceBytes(byte[] buffer, byte[] original, byte[] replacement) { + // Gotcha here: original and replacement must be the same length + assertEquals(original.length, replacement.length); + boolean found; + for(int i=0; i < buffer.length - original.length; i++) { + found = false; + if (buffer[i] == original[0]) { + found = true; + for (int j=0; j < original.length; j++) { + if (buffer[i+j] != original[j]) { + found = false; + break; + } + } + } + if (found) { + for (int j=0; j < original.length; j++) { + buffer[i+j] = replacement[j]; + } + } + } + } + + private static void writeBytes(File f, byte[] bytes) throws IOException { + FileOutputStream out = new FileOutputStream(f); + out.write(bytes); + out.close(); + } + + /** + * Make sure we don't fail silently for duplicate entries. + * b/8219321 + */ + public void testDuplicateEntries() throws Exception { + String name1 = "test_file_name1"; + String name2 = "test_file_name2"; + + // Create the good zip file. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream out = createZipOutputStream(baos); + out.putNextEntry(new ZipEntry(name2)); + out.closeEntry(); + out.putNextEntry(new ZipEntry(name1)); + out.closeEntry(); + out.close(); + + // Rewrite one of the filenames. + byte[] buffer = baos.toByteArray(); + replaceBytes(buffer, name2.getBytes(), name1.getBytes()); + + // Write the result to a file. + File badZip = createTemporaryZipFile(); + writeBytes(badZip, buffer); + + // Check that we refuse to load the modified file. + try { + ZipFile bad = new ZipFile(badZip); + fail(); + } catch (ZipException expected) { + } + } + + /** + * Make sure the size used for stored zip entires is the uncompressed size. + * b/10227498 + */ + public void testStoredEntrySize() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream out = createZipOutputStream(baos); + + // Set up a single stored entry. + String name = "test_file"; + int expectedLength = 5; + ZipEntry outEntry = new ZipEntry(name); + byte[] buffer = new byte[expectedLength]; + outEntry.setMethod(ZipEntry.STORED); + CRC32 crc = new CRC32(); + crc.update(buffer); + outEntry.setCrc(crc.getValue()); + outEntry.setSize(buffer.length); + + out.putNextEntry(outEntry); + out.write(buffer); + out.closeEntry(); + out.close(); + + // Write the result to a file. + byte[] outBuffer = baos.toByteArray(); + File zipFile = createTemporaryZipFile(); + writeBytes(zipFile, outBuffer); + + ZipFile zip = new ZipFile(zipFile); + // Set up the zip entry to have different compressed/uncompressed sizes. + ZipEntry ze = zip.getEntry(name); + ze.setCompressedSize(expectedLength - 1); + // Read the contents of the stream and verify uncompressed size was used. + InputStream stream = zip.getInputStream(ze); + int count = 0; + int read; + while ((read = stream.read(buffer)) != -1) { + count += read; + } + + assertEquals(expectedLength, count); + zip.close(); + } + + public void testInflatingStreamsRequiringZipRefill() throws IOException { + int originalSize = 1024 * 1024; + byte[] readBuffer = new byte[8192]; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */); + + ZipInputStream in = new ZipInputStream(new FileInputStream(f)); + while (in.getNextEntry() != null) { + while (in.read(readBuffer, 0, readBuffer.length) != -1) {} + } + in.close(); + } + + public void testZipFileWithLotsOfEntries() throws IOException { + int expectedEntryCount = 64*1024 - 1; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), expectedEntryCount, 0, false /* setEntrySize */); + ZipFile zipFile = new ZipFile(f); + int entryCount = 0; + for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { + ZipEntry zipEntry = e.nextElement(); + ++entryCount; + } + assertEquals(expectedEntryCount, entryCount); + zipFile.close(); + } + + // http://code.google.com/p/android/issues/detail?id=36187 + public void testZipFileLargerThan2GiB() throws IOException { + if (false) { // TODO: this test requires too much time and too much disk space! + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1024, 3*1024*1024, false /* setEntrySize */); + ZipFile zipFile = new ZipFile(f); + int entryCount = 0; + for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { + e.nextElement(); + ++entryCount; + } + assertEquals(1024, entryCount); + zipFile.close(); + } + } + + /** + * Compresses the given number of files, each of the given size, into a .zip archive. + */ + protected void writeEntries(ZipOutputStream out, int entryCount, long entrySize, + boolean setEntrySize) + throws IOException { + byte[] writeBuffer = new byte[8192]; + Random random = new Random(); + try { + for (int entry = 0; entry < entryCount; ++entry) { + ZipEntry ze = new ZipEntry(Integer.toHexString(entry)); + if (setEntrySize) { + ze.setSize(entrySize); + } + out.putNextEntry(ze); + + for (long i = 0; i < entrySize; i += writeBuffer.length) { + random.nextBytes(writeBuffer); + int byteCount = (int) Math.min(writeBuffer.length, entrySize - i); + out.write(writeBuffer, 0, byteCount); + } + + out.closeEntry(); + } + } finally { + out.close(); + } + } + + static File createTemporaryZipFile() throws IOException { + File result = File.createTempFile("ZipFileTest", ".zip"); + result.deleteOnExit(); + return result; + } + + private ZipOutputStream createZipOutputStream(File f) throws IOException { + return createZipOutputStream(new BufferedOutputStream(new FileOutputStream(f))); + } + + protected abstract ZipOutputStream createZipOutputStream(OutputStream wrapped); + + public void testSTORED() throws IOException { + ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); + CRC32 crc = new CRC32(); + + // Missing CRC, size, and compressed size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Missing CRC and compressed size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + ze.setSize(0); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Missing CRC and size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + ze.setSize(0); + ze.setCompressedSize(0); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Missing size and compressed size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Missing size is copied from compressed size. + { + ZipEntry ze = new ZipEntry("okay1"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + + assertEquals(-1, ze.getSize()); + assertEquals(-1, ze.getCompressedSize()); + + ze.setCompressedSize(0); + + assertEquals(-1, ze.getSize()); + assertEquals(0, ze.getCompressedSize()); + + out.putNextEntry(ze); + + assertEquals(0, ze.getSize()); + assertEquals(0, ze.getCompressedSize()); + } + + // Missing compressed size is copied from size. + { + ZipEntry ze = new ZipEntry("okay2"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + + assertEquals(-1, ze.getSize()); + assertEquals(-1, ze.getCompressedSize()); + + ze.setSize(0); + + assertEquals(0, ze.getSize()); + assertEquals(-1, ze.getCompressedSize()); + + out.putNextEntry(ze); + + assertEquals(0, ze.getSize()); + assertEquals(0, ze.getCompressedSize()); + } + + // Mismatched size and compressed size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + ze.setCompressedSize(1); + ze.setSize(0); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Everything present => success. + ZipEntry ze = new ZipEntry("okay"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + ze.setSize(0); + ze.setCompressedSize(0); + out.putNextEntry(ze); + + out.close(); + } + + private String makeString(int count, String ch) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; ++i) { + sb.append(ch); + } + return sb.toString(); + } + + public void testComments() throws Exception { + String expectedFileComment = "1 \u0666 2"; + String expectedEntryComment = "a \u0666 b"; + + File file = createTemporaryZipFile(); + ZipOutputStream out = createZipOutputStream(file); + + // Is file comment length checking done on bytes or characters? (Should be bytes.) + out.setComment(null); + out.setComment(makeString(0xffff, "a")); + try { + out.setComment(makeString(0xffff + 1, "a")); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + out.setComment(makeString(0xffff, "\u0666")); + fail(); + } catch (IllegalArgumentException expected) { + } + + ZipEntry ze = new ZipEntry("a"); + + // Is entry comment length checking done on bytes or characters? (Should be bytes.) + ze.setComment(null); + ze.setComment(makeString(0xffff, "a")); + try { + ze.setComment(makeString(0xffff + 1, "a")); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + ze.setComment(makeString(0xffff, "\u0666")); + fail(); + } catch (IllegalArgumentException expected) { + } + + ze.setComment(expectedEntryComment); + out.putNextEntry(ze); + out.closeEntry(); + + out.setComment(expectedFileComment); + out.close(); + + ZipFile zipFile = new ZipFile(file); + assertEquals(expectedFileComment, zipFile.getComment()); + assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment()); + zipFile.close(); + } + + public void test_getComment_unset() throws Exception { + File file = createTemporaryZipFile(); + ZipOutputStream out = createZipOutputStream(file); + ZipEntry ze = new ZipEntry("test entry"); + ze.setComment("per-entry comment"); + out.putNextEntry(ze); + out.close(); + + ZipFile zipFile = new ZipFile(file); + assertEquals(null, zipFile.getComment()); + } + + // https://code.google.com/p/android/issues/detail?id=58465 + public void test_NUL_in_filename() throws Exception { + File file = createTemporaryZipFile(); + + // We allow creation of a ZipEntry whose name contains a NUL byte, + // mainly because it's not likely to happen by accident and it's useful for testing. + ZipOutputStream out = createZipOutputStream(file); + out.putNextEntry(new ZipEntry("hello")); + out.putNextEntry(new ZipEntry("hello\u0000")); + out.close(); + + // But you can't open a ZIP file containing such an entry, because we reject it + // when we find it in the central directory. + try { + ZipFile zipFile = new ZipFile(file); + fail(); + } catch (ZipException expected) { + } + } + + public void testCrc() throws IOException { + ZipEntry ze = new ZipEntry("test"); + ze.setMethod(ZipEntry.STORED); + ze.setSize(4); + + // setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits). + try { + ze.setCrc(-1); + } catch (IllegalArgumentException expected) { + } + + // You can set the CRC32 to 0xffffffff if you're slightly more careful though... + ze.setCrc(0xffffffffL); + assertEquals(0xffffffffL, ze.getCrc()); + + // And it actually works, even though we use -1L to mean "no CRC set"... + ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); + out.putNextEntry(ze); + out.write(-1); + out.write(-1); + out.write(-1); + out.write(-1); + out.closeEntry(); + out.close(); + } + + /** + * RI does not allow reading of an empty zip using a {@link ZipFile}. + */ + public void testConstructorFailsWhenReadingEmptyZipArchive() throws IOException { + + File resources = Support_Resources.createTempFolder(); + File emptyZip = Support_Resources.copyFile( + resources, "java/util/zip", "EmptyArchive.zip"); + + try { + // The following should fail with an exception but if it doesn't then we need to clean + // up the resource so we need a reference to it. + ZipFile zipFile = new ZipFile(emptyZip); + + // Clean up the resource. + try { + zipFile.close(); + } catch (Exception e) { + // Ignore + } + fail(); + } catch (ZipException expected) { + // expected + } + } + + // Demonstrates http://b/18644314 : Zip entry names are relative to the point of + // extraction and can contain relative paths "../" and "./". + // + // It is left to callers of the API to perform any validation / santization to + // ensure that files are not written outside of the destination directory, where that + // is a concern. + public void testArchivesWithRelativePaths() throws IOException { + String[] entryNames = { + "../", + "../foo.bar", + "foo/../../", + "foo/../../bar.baz" + }; + + File zip = createTemporaryZipFile(); + ZipOutputStream out = createZipOutputStream(zip); + + try { + byte[] entryData = new byte[1024]; + for (String entryName : entryNames) { + ZipEntry ze = new ZipEntry(entryName); + out.putNextEntry(ze); + out.write(entryData); + out.closeEntry(); + } + } finally { + out.close(); + } + + ZipFile zf = new ZipFile(zip, ZipFile.OPEN_READ); + Enumeration<? extends ZipEntry> entries = zf.entries(); + Set<String> entryNamesFromFile = new HashSet<>(); + while (entries.hasMoreElements()) { + ZipEntry ze = entries.nextElement(); + entryNamesFromFile.add(ze.getName()); + } + + zf.close(); + + for (String entryName : entryNames) { + assertTrue(entryNamesFromFile.contains(entryName)); + } + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/DeflaterTest.java b/luni/src/test/java/libcore/java/util/zip/DeflaterTest.java index 30aa7f3..1dfa775 100644 --- a/luni/src/test/java/libcore/java/util/zip/DeflaterTest.java +++ b/luni/src/test/java/libcore/java/util/zip/DeflaterTest.java @@ -82,4 +82,26 @@ public class DeflaterTest extends TestCase { assertTrue(totalDeflated > 0); // the deflated form should be non-empty assertEquals(0, totalInflated); } + + public void testDeflaterCounts() throws Exception { + deflater.setInput(new byte[] { 1, 2, 3 }); + assertEquals(11, deflater.deflate(compressed, 0, compressed.length, Deflater.FULL_FLUSH)); + assertEquals(3, deflater.getBytesRead()); + assertEquals(3, deflater.getTotalIn()); + assertEquals(11, deflater.getBytesWritten()); + assertEquals(11, deflater.getTotalOut()); + + deflater.setInput(new byte[] { 1, 2, 3 }); + assertEquals(9, deflater.deflate(compressed, 0, compressed.length, Deflater.FULL_FLUSH)); + assertEquals(6, deflater.getBytesRead()); + assertEquals(6, deflater.getTotalIn()); + assertEquals(20, deflater.getBytesWritten()); + + + deflater.reset(); + assertEquals(0, deflater.getBytesRead()); + assertEquals(0, deflater.getBytesWritten()); + assertEquals(0, deflater.getTotalIn()); + assertEquals(0, deflater.getTotalOut()); + } } diff --git a/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java index 494520a..5813753 100644 --- a/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java +++ b/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java @@ -35,14 +35,46 @@ import libcore.io.Streams; public final class GZIPInputStreamTest extends TestCase { private static final byte[] HELLO_WORLD_GZIPPED = new byte[] { - 31, -117, 8, 0, 0, 0, 0, 0, 0, 0, -13, 72, -51, -55, -55, 87, 8, -49, - 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 + 31, -117, 8, 0, 0, 0, 0, 0, 0, 0, // 10 byte header + -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 // data + }; + + /** + * This is the same as the above, except that the 4th header byte is 2 (FHCRC flag) + * and the 2 bytes after the header make up the CRC. + * + * Constructed manually because none of the commonly used tools appear to emit header CRCs. + */ + private static final byte[] HELLO_WORLD_GZIPPED_WITH_HEADER_CRC = new byte[] { + 31, -117, 8, 2, 0, 0, 0, 0, 0, 0, // 10 byte header + 29, 38, // 2 byte CRC. + -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 // data + }; + + /*( + * This is the same as {@code HELLO_WORLD_GZIPPED} except that the 4th header byte is 4 + * (FEXTRA flag) and that the 8 bytes after the header make up the extra. + * + * Constructed manually because none of the commonly used tools appear to emit header CRCs. + */ + private static final byte[] HELLO_WORLD_GZIPPED_WITH_EXTRA = new byte[] { + 31, -117, 8, 4, 0, 0, 0, 0, 0, 0, // 10 byte header + 6, 0, 4, 2, 4, 2, 4, 2, // 2 byte extra length + 6 byte extra. + -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 // data }; public void testShortMessage() throws IOException { assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED), "UTF-8")); } + public void testShortMessageWithCrc() throws IOException { + assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED_WITH_HEADER_CRC), "UTF-8")); + } + + public void testShortMessageWithHeaderExtra() throws IOException { + assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED_WITH_EXTRA), "UTF-8")); + } + public void testLongMessage() throws IOException { byte[] data = new byte[1024 * 1024]; new Random().nextBytes(data); diff --git a/luni/src/test/java/libcore/java/util/zip/InflaterTest.java b/luni/src/test/java/libcore/java/util/zip/InflaterTest.java index 158b3e9..cce08f3 100644 --- a/luni/src/test/java/libcore/java/util/zip/InflaterTest.java +++ b/luni/src/test/java/libcore/java/util/zip/InflaterTest.java @@ -122,4 +122,35 @@ public class InflaterTest extends TestCase { adler32.update(bytes); return (int) adler32.getValue(); } + + public void testInflaterCounts() throws Exception { + Inflater inflater = new Inflater(); + + byte[] decompressed = new byte[32]; + byte[] compressed = deflate(new byte[] { 1, 2, 3}, null); + assertEquals(11, compressed.length); + + // Feed in bytes [0, 5) to the first iteration. + inflater.setInput(compressed, 0, 5); + inflater.inflate(decompressed, 0, decompressed.length); + assertEquals(5, inflater.getBytesRead()); + assertEquals(5, inflater.getTotalIn()); + assertEquals(2, inflater.getBytesWritten()); + assertEquals(2, inflater.getTotalOut()); + + // Feed in bytes [5, 11) to the second iteration. + assertEquals(true, inflater.needsInput()); + inflater.setInput(compressed, 5, 6); + assertEquals(1, inflater.inflate(decompressed, 0, decompressed.length)); + assertEquals(11, inflater.getBytesRead()); + assertEquals(11, inflater.getTotalIn()); + assertEquals(3, inflater.getBytesWritten()); + assertEquals(3, inflater.getTotalOut()); + + inflater.reset(); + assertEquals(0, inflater.getBytesRead()); + assertEquals(0, inflater.getTotalIn()); + assertEquals(0, inflater.getBytesWritten()); + assertEquals(0, inflater.getTotalOut()); + } } diff --git a/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java b/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java new file mode 100644 index 0000000..1b733f9 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java @@ -0,0 +1,102 @@ +/* + * 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.java.util.zip; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public final class Zip64FileTest extends AbstractZipFileTest { + @Override + protected ZipOutputStream createZipOutputStream(OutputStream wrapped) { + return new ZipOutputStream(wrapped, true /* forceZip64 */); + } + + public void testZip64Support_largeNumberOfEntries() throws IOException { + final File file = createZipFile(65550, 2, false /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(65550, zf.size()); + + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(2, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + public void testZip64Support_totalLargerThan4G() throws IOException { + final File file = createZipFile(5, 1073741824L, false /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(5, zf.size()); + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(1073741824L, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + public void testZip64Support_hugeEntry() throws IOException { + try { + createZipFile(1, 4294967410L, false /* setEntrySize */); + fail(); + } catch (IOException expected) { + } + + final File file = createZipFile(1, 4294967410L, true /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(1, zf.size()); + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(4294967410L, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + private File createZipFile(int numEntries, long entrySize, boolean setEntrySize) + throws IOException { + File file = createTemporaryZipFile(); + // Don't force a 64 bit zip file to test that our heuristics work. + ZipOutputStream os = new ZipOutputStream( + new BufferedOutputStream(new FileOutputStream(file))); + writeEntries(os, numEntries, entrySize, setEntrySize); + return file; + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java b/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java index 550ddfb..c7667b9 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.List; import java.util.jar.JarEntry; import java.util.zip.ZipEntry; +import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; @@ -132,6 +133,7 @@ public class ZipEntryTest extends junit.framework.TestCase { File f = createTemporaryZipFile(); ZipOutputStream out = createZipOutputStream(f); ZipEntry ze = new ZipEntry("x"); + ze.setSize(0); ze.setExtra(maxLengthExtra); out.putNextEntry(ze); out.closeEntry(); @@ -143,6 +145,25 @@ public class ZipEntryTest extends junit.framework.TestCase { zipFile.close(); } + public void testMaxLengthExtra_zip64() throws Exception { + // Not quite the max length (65535), but large enough that there's no space + // for the zip64 extended info header. + byte[] maxLengthExtra = new byte[65530]; + + File f = createTemporaryZipFile(); + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)), + true /* forceZip64 */); + ZipEntry ze = new ZipEntry("x"); + + ze.setExtra(maxLengthExtra); + try { + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + } + + public void testTooLongComment() throws Exception { String tooLongComment = makeString(65536, "z"); ZipEntry ze = new ZipEntry("x"); @@ -176,7 +197,17 @@ public class ZipEntryTest extends junit.framework.TestCase { File f = createTemporaryZipFile(); ZipOutputStream out = createZipOutputStream(f); + + // Regular (non zip64) format. ZipEntry ze = new ZipEntry("x"); + ze.setSize(0); + ze.setExtra(extra); + ze.setComment(comment); + out.putNextEntry(ze); + out.closeEntry(); + + // An entry without a length is assumed to be zip64. + ze = new ZipEntry("y"); ze.setExtra(extra); ze.setComment(comment); out.putNextEntry(ze); @@ -188,6 +219,9 @@ public class ZipEntryTest extends junit.framework.TestCase { try { assertEquals(comment, zipFile.getEntry("x").getComment()); assertTrue(Arrays.equals(extra, zipFile.getEntry("x").getExtra())); + + assertEquals(comment, zipFile.getEntry("y").getComment()); + assertTrue(Arrays.equals(extra, zipFile.getEntry("y").getExtra())); } finally { zipFile.close(); } diff --git a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java index a9ff56f..02210ac 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -11,497 +11,18 @@ * 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. + * limitations under the License */ package libcore.java.util.zip; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -import junit.framework.TestCase; -import tests.support.resource.Support_Resources; +public final class ZipFileTest extends AbstractZipFileTest { -public final class ZipFileTest extends TestCase { - /** - * Exercise Inflater's ability to refill the zlib's input buffer. As of this - * writing, this buffer's max size is 64KiB compressed bytes. We'll write a - * full megabyte of uncompressed data, which should be sufficient to exhaust - * the buffer. http://b/issue?id=2734751 - */ - public void testInflatingFilesRequiringZipRefill() throws IOException { - int originalSize = 1024 * 1024; - byte[] readBuffer = new byte[8192]; - ZipFile zipFile = new ZipFile(createZipFile(1, originalSize)); - for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { - ZipEntry zipEntry = e.nextElement(); - assertTrue("This test needs >64 KiB of compressed data to exercise Inflater", - zipEntry.getCompressedSize() > (64 * 1024)); - InputStream is = zipFile.getInputStream(zipEntry); - while (is.read(readBuffer, 0, readBuffer.length) != -1) {} - is.close(); - } - zipFile.close(); - } - - private static void replaceBytes(byte[] buffer, byte[] original, byte[] replacement) { - // Gotcha here: original and replacement must be the same length - assertEquals(original.length, replacement.length); - boolean found; - for(int i=0; i < buffer.length - original.length; i++) { - found = false; - if (buffer[i] == original[0]) { - found = true; - for (int j=0; j < original.length; j++) { - if (buffer[i+j] != original[j]) { - found = false; - break; - } - } - } - if (found) { - for (int j=0; j < original.length; j++) { - buffer[i+j] = replacement[j]; - } - } - } - } - - private static void writeBytes(File f, byte[] bytes) throws IOException { - FileOutputStream out = new FileOutputStream(f); - out.write(bytes); - out.close(); - } - - /** - * Make sure we don't fail silently for duplicate entries. - * b/8219321 - */ - public void testDuplicateEntries() throws Exception { - String name1 = "test_file_name1"; - String name2 = "test_file_name2"; - - // Create the good zip file. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ZipOutputStream out = new ZipOutputStream(baos); - out.putNextEntry(new ZipEntry(name2)); - out.closeEntry(); - out.putNextEntry(new ZipEntry(name1)); - out.closeEntry(); - out.close(); - - // Rewrite one of the filenames. - byte[] buffer = baos.toByteArray(); - replaceBytes(buffer, name2.getBytes(), name1.getBytes()); - - // Write the result to a file. - File badZip = createTemporaryZipFile(); - writeBytes(badZip, buffer); - - // Check that we refuse to load the modified file. - try { - ZipFile bad = new ZipFile(badZip); - fail(); - } catch (ZipException expected) { - } - } - - /** - * Make sure the size used for stored zip entires is the uncompressed size. - * b/10227498 - */ - public void testStoredEntrySize() throws Exception { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ZipOutputStream out = new ZipOutputStream(baos); - - // Set up a single stored entry. - String name = "test_file"; - int expectedLength = 5; - ZipEntry outEntry = new ZipEntry(name); - byte[] buffer = new byte[expectedLength]; - outEntry.setMethod(ZipEntry.STORED); - CRC32 crc = new CRC32(); - crc.update(buffer); - outEntry.setCrc(crc.getValue()); - outEntry.setSize(buffer.length); - - out.putNextEntry(outEntry); - out.write(buffer); - out.closeEntry(); - out.close(); - - // Write the result to a file. - byte[] outBuffer = baos.toByteArray(); - File zipFile = createTemporaryZipFile(); - writeBytes(zipFile, outBuffer); - - ZipFile zip = new ZipFile(zipFile); - // Set up the zip entry to have different compressed/uncompressed sizes. - ZipEntry ze = zip.getEntry(name); - ze.setCompressedSize(expectedLength - 1); - // Read the contents of the stream and verify uncompressed size was used. - InputStream stream = zip.getInputStream(ze); - int count = 0; - int read; - while ((read = stream.read(buffer)) != -1) { - count += read; - } - - assertEquals(expectedLength, count); - zip.close(); - } - - public void testInflatingStreamsRequiringZipRefill() throws IOException { - int originalSize = 1024 * 1024; - byte[] readBuffer = new byte[8192]; - ZipInputStream in = new ZipInputStream(new FileInputStream(createZipFile(1, originalSize))); - while (in.getNextEntry() != null) { - while (in.read(readBuffer, 0, readBuffer.length) != -1) {} - } - in.close(); - } - - public void testZipFileWithLotsOfEntries() throws IOException { - int expectedEntryCount = 64*1024 - 1; - File f = createZipFile(expectedEntryCount, 0); - ZipFile zipFile = new ZipFile(f); - int entryCount = 0; - for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { - ZipEntry zipEntry = e.nextElement(); - ++entryCount; - } - assertEquals(expectedEntryCount, entryCount); - zipFile.close(); - } - - // http://code.google.com/p/android/issues/detail?id=36187 - public void testZipFileLargerThan2GiB() throws IOException { - if (false) { // TODO: this test requires too much time and too much disk space! - File f = createZipFile(1024, 3*1024*1024); - ZipFile zipFile = new ZipFile(f); - int entryCount = 0; - for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { - ZipEntry zipEntry = e.nextElement(); - ++entryCount; - } - assertEquals(1024, entryCount); - zipFile.close(); - } - } - - public void testZip64Support() throws IOException { - try { - createZipFile(64*1024, 0); - fail(); // Make this test more like testHugeZipFile when we have Zip64 support. - } catch (ZipException expected) { - } - } - - /** - * Compresses the given number of files, each of the given size, into a .zip archive. - */ - private File createZipFile(int entryCount, int entrySize) throws IOException { - File result = createTemporaryZipFile(); - - byte[] writeBuffer = new byte[8192]; - Random random = new Random(); - - ZipOutputStream out = createZipOutputStream(result); - try { - for (int entry = 0; entry < entryCount; ++entry) { - ZipEntry ze = new ZipEntry(Integer.toHexString(entry)); - out.putNextEntry(ze); - - for (int i = 0; i < entrySize; i += writeBuffer.length) { - random.nextBytes(writeBuffer); - int byteCount = Math.min(writeBuffer.length, entrySize - i); - out.write(writeBuffer, 0, byteCount); - } - - out.closeEntry(); - } - } finally { - out.close(); - } - return result; - } - - private File createTemporaryZipFile() throws IOException { - File result = File.createTempFile("ZipFileTest", "zip"); - result.deleteOnExit(); - return result; - } - - private ZipOutputStream createZipOutputStream(File f) throws IOException { - return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f))); - } - - public void testSTORED() throws IOException { - ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); - CRC32 crc = new CRC32(); - - // Missing CRC, size, and compressed size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Missing CRC and compressed size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - ze.setSize(0); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Missing CRC and size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - ze.setSize(0); - ze.setCompressedSize(0); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Missing size and compressed size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Missing size is copied from compressed size. - { - ZipEntry ze = new ZipEntry("okay1"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - - assertEquals(-1, ze.getSize()); - assertEquals(-1, ze.getCompressedSize()); - - ze.setCompressedSize(0); - - assertEquals(-1, ze.getSize()); - assertEquals(0, ze.getCompressedSize()); - - out.putNextEntry(ze); - - assertEquals(0, ze.getSize()); - assertEquals(0, ze.getCompressedSize()); - } - - // Missing compressed size is copied from size. - { - ZipEntry ze = new ZipEntry("okay2"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - - assertEquals(-1, ze.getSize()); - assertEquals(-1, ze.getCompressedSize()); - - ze.setSize(0); - - assertEquals(0, ze.getSize()); - assertEquals(-1, ze.getCompressedSize()); - - out.putNextEntry(ze); - - assertEquals(0, ze.getSize()); - assertEquals(0, ze.getCompressedSize()); - } - - // Mismatched size and compressed size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - ze.setCompressedSize(1); - ze.setSize(0); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Everything present => success. - ZipEntry ze = new ZipEntry("okay"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - ze.setSize(0); - ze.setCompressedSize(0); - out.putNextEntry(ze); - - out.close(); - } - - private String makeString(int count, String ch) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < count; ++i) { - sb.append(ch); - } - return sb.toString(); - } - - public void testComments() throws Exception { - String expectedFileComment = "1 \u0666 2"; - String expectedEntryComment = "a \u0666 b"; - - File file = createTemporaryZipFile(); - ZipOutputStream out = createZipOutputStream(file); - - // Is file comment length checking done on bytes or characters? (Should be bytes.) - out.setComment(null); - out.setComment(makeString(0xffff, "a")); - try { - out.setComment(makeString(0xffff + 1, "a")); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - out.setComment(makeString(0xffff, "\u0666")); - fail(); - } catch (IllegalArgumentException expected) { - } - - ZipEntry ze = new ZipEntry("a"); - - // Is entry comment length checking done on bytes or characters? (Should be bytes.) - ze.setComment(null); - ze.setComment(makeString(0xffff, "a")); - try { - ze.setComment(makeString(0xffff + 1, "a")); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - ze.setComment(makeString(0xffff, "\u0666")); - fail(); - } catch (IllegalArgumentException expected) { - } - - ze.setComment(expectedEntryComment); - out.putNextEntry(ze); - out.closeEntry(); - - out.setComment(expectedFileComment); - out.close(); - - ZipFile zipFile = new ZipFile(file); - assertEquals(expectedFileComment, zipFile.getComment()); - assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment()); - zipFile.close(); - } - - public void test_getComment_unset() throws Exception { - File file = createTemporaryZipFile(); - ZipOutputStream out = createZipOutputStream(file); - ZipEntry ze = new ZipEntry("test entry"); - ze.setComment("per-entry comment"); - out.putNextEntry(ze); - out.close(); - - ZipFile zipFile = new ZipFile(file); - assertEquals(null, zipFile.getComment()); - } - - // https://code.google.com/p/android/issues/detail?id=58465 - public void test_NUL_in_filename() throws Exception { - File file = createTemporaryZipFile(); - - // We allow creation of a ZipEntry whose name contains a NUL byte, - // mainly because it's not likely to happen by accident and it's useful for testing. - ZipOutputStream out = createZipOutputStream(file); - out.putNextEntry(new ZipEntry("hello")); - out.putNextEntry(new ZipEntry("hello\u0000")); - out.close(); - - // But you can't open a ZIP file containing such an entry, because we reject it - // when we find it in the central directory. - try { - ZipFile zipFile = new ZipFile(file); - fail(); - } catch (ZipException expected) { - } - } - - public void testCrc() throws IOException { - ZipEntry ze = new ZipEntry("test"); - ze.setMethod(ZipEntry.STORED); - ze.setSize(4); - - // setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits). - try { - ze.setCrc(-1); - } catch (IllegalArgumentException expected) { - } - - // You can set the CRC32 to 0xffffffff if you're slightly more careful though... - ze.setCrc(0xffffffffL); - assertEquals(0xffffffffL, ze.getCrc()); - - // And it actually works, even though we use -1L to mean "no CRC set"... - ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); - out.putNextEntry(ze); - out.write(-1); - out.write(-1); - out.write(-1); - out.write(-1); - out.closeEntry(); - out.close(); - } - - /** - * RI does not allow reading of an empty zip using a {@link ZipFile}. - */ - public void testConstructorFailsWhenReadingEmptyZipArchive() throws IOException { - - File resources = Support_Resources.createTempFolder(); - File emptyZip = Support_Resources.copyFile( - resources, "java/util/zip", "EmptyArchive.zip"); - - try { - // The following should fail with an exception but if it doesn't then we need to clean - // up the resource so we need a reference to it. - ZipFile zipFile = new ZipFile(emptyZip); - - // Clean up the resource. - try { - zipFile.close(); - } catch (Exception e) { - // Ignore - } - fail(); - } catch (ZipException expected) { - // expected - } + @Override + protected ZipOutputStream createZipOutputStream(OutputStream wrapped) { + return new ZipOutputStream(wrapped); } } diff --git a/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java index e69f010..15600de 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java @@ -21,7 +21,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Random; import java.util.zip.ZipEntry; diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java index c89886c..494d15e 100644 --- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java +++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java @@ -847,6 +847,24 @@ public final class CipherTest extends TestCase { } } + public void testCipher_getInstance_DoesNotSupportKeyClass_Success() throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName()); + put("Cipher.FOO SupportedKeyClasses", "None"); + } + }; + + Security.addProvider(mockProvider); + try { + Cipher c = Cipher.getInstance("FOO", mockProvider); + c.init(Cipher.ENCRYPT_MODE, new MockKey()); + assertEquals(mockProvider, c.getProvider()); + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + public void testCipher_getInstance_SuppliedProviderNotRegistered_MultipartTransform_Success() throws Exception { Provider mockProvider = new MockProvider("MockProvider") { @@ -1136,6 +1154,9 @@ public final class CipherTest extends TestCase { final AlgorithmParameterSpec decryptSpec = getDecryptAlgorithmParameterSpec(encryptSpec, c); int decryptMode = getDecryptMode(algorithm); + + test_Cipher_init_Decrypt_NullParameters(c, decryptMode, encryptKey, decryptSpec != null); + c.init(decryptMode, encryptKey, decryptSpec); assertEquals(cipherID + " getBlockSize() decryptMode", getExpectedBlockSize(algorithm, decryptMode, providerName), c.getBlockSize()); @@ -1268,6 +1289,53 @@ public final class CipherTest extends TestCase { } } + private void test_Cipher_init_Decrypt_NullParameters(Cipher c, int decryptMode, Key encryptKey, + boolean needsParameters) throws Exception { + try { + c.init(decryptMode, encryptKey, (AlgorithmParameterSpec) null); + if (needsParameters) { + fail("Should throw InvalidAlgorithmParameterException with null parameters"); + } + } catch (InvalidAlgorithmParameterException e) { + if (!needsParameters) { + throw e; + } + } + + try { + c.init(decryptMode, encryptKey, (AlgorithmParameterSpec) null, (SecureRandom) null); + if (needsParameters) { + fail("Should throw InvalidAlgorithmParameterException with null parameters"); + } + } catch (InvalidAlgorithmParameterException e) { + if (!needsParameters) { + throw e; + } + } + + try { + c.init(decryptMode, encryptKey, (AlgorithmParameters) null); + if (needsParameters) { + fail("Should throw InvalidAlgorithmParameterException with null parameters"); + } + } catch (InvalidAlgorithmParameterException e) { + if (!needsParameters) { + throw e; + } + } + + try { + c.init(decryptMode, encryptKey, (AlgorithmParameters) null, (SecureRandom) null); + if (needsParameters) { + fail("Should throw InvalidAlgorithmParameterException with null parameters"); + } + } catch (InvalidAlgorithmParameterException e) { + if (!needsParameters) { + throw e; + } + } + } + public void testInputPKCS1Padding() throws Exception { for (String provider : RSA_PROVIDERS) { testInputPKCS1Padding(provider); @@ -2797,6 +2865,17 @@ public final class CipherTest extends TestCase { } } + public void testCipher_Update_WithZeroLengthInput_ReturnsNull() throws Exception { + SecretKey key = new SecretKeySpec(AES_128_KEY, "AES"); + Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); + c.init(Cipher.ENCRYPT_MODE, key); + assertNull(c.update(new byte[0])); + assertNull(c.update(new byte[c.getBlockSize() * 2], 0, 0)); + + // Try with non-zero offset just in case the implementation mixes up offset and inputLen + assertNull(c.update(new byte[c.getBlockSize() * 2], 16, 0)); + } + private void checkCipher_ShortBlock_Failure(CipherTestParam p, String provider) throws Exception { SecretKey key = new SecretKeySpec(p.key, "AES"); Cipher c = Cipher.getInstance( diff --git a/luni/src/test/java/libcore/javax/crypto/KeyAgreementTest.java b/luni/src/test/java/libcore/javax/crypto/KeyAgreementTest.java new file mode 100644 index 0000000..ba03f75 --- /dev/null +++ b/luni/src/test/java/libcore/javax/crypto/KeyAgreementTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 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.javax.crypto; + +import java.security.Provider; +import java.security.Security; + +import javax.crypto.KeyAgreement; + +import junit.framework.TestCase; + +public class KeyAgreementTest extends TestCase { + private static abstract class MockProvider extends Provider { + public MockProvider(String name) { + super(name, 1.0, "Mock provider used for testing"); + setup(); + } + + public abstract void setup(); + } + + public void testKeyAgreement_getInstance_SuppliedProviderNotRegistered_Success() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("KeyAgreement.FOO", MockKeyAgreementSpi.AllKeyTypes.class.getName()); + } + }; + + { + KeyAgreement c = KeyAgreement.getInstance("FOO", mockProvider); + c.init(new MockKey()); + assertEquals(mockProvider, c.getProvider()); + } + } + + public void testKeyAgreement_getInstance_DoesNotSupportKeyClass_Success() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("KeyAgreement.FOO", MockKeyAgreementSpi.AllKeyTypes.class.getName()); + put("KeyAgreement.FOO SupportedKeyClasses", "none"); + + } + }; + + Security.addProvider(mockProvider); + try { + KeyAgreement c = KeyAgreement.getInstance("FOO", mockProvider); + c.init(new MockKey()); + assertEquals(mockProvider, c.getProvider()); + } finally { + Security.removeProvider(mockProvider.getName()); + } + } +} diff --git a/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java b/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java index 8bbd548..5763562 100644 --- a/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java +++ b/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java @@ -44,6 +44,15 @@ public class KeyGeneratorTest extends TestCase { if (!type.equals("KeyGenerator")) { continue; } + + // Do not test AndroidKeyStore's KeyGenerator. It cannot be initialized without + // providing AndroidKeyStore-specific algorithm parameters. + // It's OKish not to test AndroidKeyStore's KeyGenerator here because it's tested + // by cts/tests/test/keystore. + if ("AndroidKeyStore".equals(provider.getName())) { + continue; + } + String algorithm = service.getAlgorithm(); try { // KeyGenerator.getInstance(String) diff --git a/luni/src/test/java/libcore/javax/crypto/MockKeyAgreementSpi.java b/luni/src/test/java/libcore/javax/crypto/MockKeyAgreementSpi.java new file mode 100644 index 0000000..574dbeb --- /dev/null +++ b/luni/src/test/java/libcore/javax/crypto/MockKeyAgreementSpi.java @@ -0,0 +1,91 @@ +/* + * Copyright 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.javax.crypto; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import javax.crypto.KeyAgreementSpi; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; + +/** + * Mock KeyAgreementSpi used by {@link KeyAgreementTest}. + */ +public class MockKeyAgreementSpi extends KeyAgreementSpi { + public static class SpecificKeyTypes extends MockKeyAgreementSpi { + @Override + public void checkKeyType(Key key) throws InvalidKeyException { + if (!(key instanceof MockKey)) { + throw new InvalidKeyException("Must be MockKey!"); + } + } + } + + public static class SpecificKeyTypes2 extends MockKeyAgreementSpi { + @Override + public void checkKeyType(Key key) throws InvalidKeyException { + System.err.println("Checking key of type " + key.getClass().getName()); + if (!(key instanceof MockKey2)) { + throw new InvalidKeyException("Must be MockKey2!"); + } + } + } + + public static class AllKeyTypes extends MockKeyAgreementSpi { + } + + public void checkKeyType(Key key) throws InvalidKeyException { + } + + @Override + protected Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException, + IllegalStateException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected byte[] engineGenerateSecret() throws IllegalStateException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected int engineGenerateSecret(byte[] sharedSecret, int offset) + throws IllegalStateException, ShortBufferException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected SecretKey engineGenerateSecret(String algorithm) throws IllegalStateException, + NoSuchAlgorithmException, InvalidKeyException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException { + checkKeyType(key); + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + checkKeyType(key); + } +} diff --git a/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java b/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java index feecebe..07ecd12 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java @@ -22,6 +22,7 @@ import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.security.PublicKey; +import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -30,21 +31,32 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Set; -import javax.net.ssl.DefaultHostnameVerifier; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; import javax.security.auth.x500.X500Principal; import junit.framework.TestCase; +/** + * Tests for the platform-default {@link HostnameVerifier} as provided by + * {@link HttpsURLConnection#getDefaultHostnameVerifier()}. + */ public final class DefaultHostnameVerifierTest extends TestCase { private static final int ALT_UNKNOWN = 0; private static final int ALT_DNS_NAME = 2; private static final int ALT_IPA_NAME = 7; - private final DefaultHostnameVerifier verifier = new DefaultHostnameVerifier(); + private final HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier(); public void testVerify() { - assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("cn=imap.g.com"))); - assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("cn=imap2.g.com"))); - assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("cn=sub.imap.g.com"))); + assertTrue(verifyWithServerCertificate( + "imap.g.com", new StubX509Certificate("cn=imap.g.com"))); + assertFalse(verifyWithServerCertificate( + "imap.g.com", new StubX509Certificate("cn=imap2.g.com"))); + assertFalse(verifyWithServerCertificate( + "imap.g.com", new StubX509Certificate("cn=sub.imap.g.com"))); } /** @@ -52,32 +64,33 @@ public final class DefaultHostnameVerifierTest extends TestCase { * be used as the identity and the CN should be ignored. */ public void testSubjectAltNameAndCn() { - assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("") - .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com"))); - assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("cn=imap.g.com") + assertFalse(verifyWithServerCertificate("imap.g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com"))); - assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("") + assertFalse( + verifyWithServerCertificate("imap.g.com", new StubX509Certificate("cn=imap.g.com") + .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com"))); + assertTrue(verifyWithServerCertificate("imap.g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com"))); } public void testSubjectAltNameWithWildcard() { - assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("imap.g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_DNS_NAME, "*.g.com"))); } public void testSubjectAltNameWithIpAddress() { - assertTrue(verifier.verify("1.2.3.4", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("1.2.3.4", new StubX509Certificate("") .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4"))); - assertFalse(verifier.verify("1.2.3.5", new StubX509Certificate("") + assertFalse(verifyWithServerCertificate("1.2.3.5", new StubX509Certificate("") .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4"))); - assertTrue(verifier.verify("192.168.100.1", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("192.168.100.1", new StubX509Certificate("") .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4") .addSubjectAlternativeName(ALT_IPA_NAME, "192.168.100.1"))); } public void testUnknownSubjectAltName() { // Has unknown subject alternative names - assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("imap.g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") @@ -85,7 +98,7 @@ public final class DefaultHostnameVerifierTest extends TestCase { .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3"))); - assertTrue(verifier.verify("2.33.44.55", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("2.33.44.55", new StubX509Certificate("") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") @@ -93,7 +106,7 @@ public final class DefaultHostnameVerifierTest extends TestCase { .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3"))); - assertFalse(verifier.verify("g.com", new StubX509Certificate("") + assertFalse(verifyWithServerCertificate("g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") @@ -101,7 +114,7 @@ public final class DefaultHostnameVerifierTest extends TestCase { .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3"))); - assertFalse(verifier.verify("2.33.44.1", new StubX509Certificate("") + assertFalse(verifyWithServerCertificate("2.33.44.1", new StubX509Certificate("") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") @@ -111,40 +124,129 @@ public final class DefaultHostnameVerifierTest extends TestCase { .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3"))); } - public void testWildcardMatchesWildcardSuffix() { - assertTrue(verifier.verifyHostName("b.c.d", "*.b.c.d")); - assertTrue(verifier.verifyHostName("imap.google.com", "*.imap.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com.au", "*.imap.google.com")); + public void testWildcardsRejectedForIpAddress() { + assertFalse(verifyWithServerCertificate("1.2.3.4", new StubX509Certificate("cn=*.2.3.4"))); + assertFalse(verifyWithServerCertificate("1.2.3.4", new StubX509Certificate("cn=*.2.3.4") + .addSubjectAlternativeName(ALT_IPA_NAME, "*.2.3.4") + .addSubjectAlternativeName(ALT_DNS_NAME, "*.2.3.4"))); + assertFalse(verifyWithServerCertificate( + "2001:1234::1", new StubX509Certificate("cn=*:1234::1"))); + assertFalse(verifyWithServerCertificate( + "2001:1234::1", new StubX509Certificate("cn=*:1234::1") + .addSubjectAlternativeName(ALT_IPA_NAME, "*:1234::1") + .addSubjectAlternativeName(ALT_DNS_NAME, "*:1234::1"))); + } + + public void testNullParameters() { + // Confirm that neither of the parameters used later in the test cause the verifier to blow + // up + String hostname = "www.example.com"; + StubSSLSession session = new StubSSLSession(); + session.peerCertificates = + new Certificate[] {new StubX509Certificate("cn=www.example.com")}; + verifier.verify(hostname, session); + + try { + verifier.verify(hostname, null); + fail(); + } catch (NullPointerException expected) { + } + + try { + verifier.verify(null, session); + fail(); + } catch (NullPointerException expected) { + } + } + + public void testInvalidDomainNames() { + assertFalse(verifyWithDomainNamePattern("", "")); + assertFalse(verifyWithDomainNamePattern(".test.example.com", ".test.example.com")); + assertFalse(verifyWithDomainNamePattern("ex*ample.com", "ex*ample.com")); + assertFalse(verifyWithDomainNamePattern("example.com..", "example.com.")); + assertFalse(verifyWithDomainNamePattern("example.com.", "example.com..")); + } + + public void testWildcardCharacterMustBeLeftMostLabelOnly() { + assertFalse(verifyWithDomainNamePattern("test.www.example.com", "test.*.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "www.*.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "www.example.*")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "*www.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "*w.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "w*w.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "w*.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "www*.example.com")); } - public void testWildcardMatchingSubstring() { - assertTrue(verifier.verifyHostName("b.c.d", "b*.c.d")); - assertTrue(verifier.verifyHostName("imap.google.com", "ima*.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com.au", "ima*.google.com")); + public void testWildcardCannotMatchEmptyLabel() { + assertFalse(verifyWithDomainNamePattern("example.com", "*.example.com")); + assertFalse(verifyWithDomainNamePattern(".example.com", "*.example.com")); } - public void testWildcardMatchingEmptySubstring() { - assertTrue(verifier.verifyHostName("imap.google.com", "imap*.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com.au", "imap*.google.com")); + public void testWildcardCannotMatchChildDomain() { + assertFalse(verifyWithDomainNamePattern("sub.www.example.com", "*.example.com")); } - public void testWildcardMatchesChildDomain() { - assertFalse(verifier.verifyHostName("a.b.c.d", "*.c.d")); + public void testWildcardRejectedForSingleLabelPatterns() { + assertFalse(verifyWithDomainNamePattern("d", "*")); + assertFalse(verifyWithDomainNamePattern("d.", "*.")); + assertFalse(verifyWithDomainNamePattern("d", "d*")); + assertFalse(verifyWithDomainNamePattern("d.", "d*.")); + assertFalse(verifyWithDomainNamePattern("d", "*d")); + assertFalse(verifyWithDomainNamePattern("d.", "*d.")); + assertFalse(verifyWithDomainNamePattern("ddd", "d*d")); + assertFalse(verifyWithDomainNamePattern("ddd.", "d*d.")); + } + + public void testNoPrefixMatch() { + assertFalse(verifyWithDomainNamePattern("imap.google.com.au", "imap.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com.au", "*.google.com")); } public void testVerifyHostName() { - assertTrue(verifier.verifyHostName("a.b.c.d", "a.b.c.d")); - assertTrue(verifier.verifyHostName("a.b.c.d", "*.b.c.d")); - assertFalse(verifier.verifyHostName("a.b.c.d", "*.*.c.d")); - assertTrue(verifier.verifyHostName("imap.google.com", "imap.google.com")); - assertFalse(verifier.verifyHostName("imap2.google.com", "imap.google.com")); - assertTrue(verifier.verifyHostName("imap.google.com", "*.google.com")); - assertTrue(verifier.verifyHostName("imap2.google.com", "*.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com", "*.googl.com")); - assertFalse(verifier.verifyHostName("imap2.google2.com", "*.google3.com")); - assertFalse(verifier.verifyHostName("imap.google.com", "a*.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com", "ix*.google.com")); - assertTrue(verifier.verifyHostName("imap.google.com", "iMap.Google.Com")); + assertTrue(verifyWithDomainNamePattern("a.b.c.d", "a.b.c.d")); + assertTrue(verifyWithDomainNamePattern("a.b.c.d", "*.b.c.d")); + assertFalse(verifyWithDomainNamePattern("a.b.c.d", "*.*.c.d")); + assertTrue(verifyWithDomainNamePattern("imap.google.com", "imap.google.com")); + assertFalse(verifyWithDomainNamePattern("imap2.google.com", "imap.google.com")); + assertTrue(verifyWithDomainNamePattern("imap.google.com", "*.google.com")); + assertTrue(verifyWithDomainNamePattern("imap2.google.com", "*.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com", "*.googl.com")); + assertFalse(verifyWithDomainNamePattern("imap2.google2.com", "*.google3.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com", "a*.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com", "ix*.google.com")); + assertTrue(verifyWithDomainNamePattern("imap.google.com", "iMap.Google.Com")); + assertTrue(verifyWithDomainNamePattern("weird", "weird")); + assertTrue(verifyWithDomainNamePattern("weird", "weird.")); + + // Wildcards rejected for domain names consisting of fewer than two labels (excluding root). + assertFalse(verifyWithDomainNamePattern("weird", "weird*")); + assertFalse(verifyWithDomainNamePattern("weird", "*weird")); + assertFalse(verifyWithDomainNamePattern("weird", "weird*.")); + assertFalse(verifyWithDomainNamePattern("weird", "weird.*")); + } + + public void testVerifyAbsoluteHostName() { + assertTrue(verifyWithDomainNamePattern("a.b.c.d.", "a.b.c.d")); + assertTrue(verifyWithDomainNamePattern("a.b.c.d.", "*.b.c.d")); + assertFalse(verifyWithDomainNamePattern("a.b.c.d.", "*.*.c.d")); + assertTrue(verifyWithDomainNamePattern("imap.google.com.", "imap.google.com")); + assertFalse(verifyWithDomainNamePattern("imap2.google.com.", "imap.google.com")); + assertTrue(verifyWithDomainNamePattern("imap.google.com.", "*.google.com")); + assertTrue(verifyWithDomainNamePattern("imap2.google.com.", "*.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com.", "*.googl.com")); + assertFalse(verifyWithDomainNamePattern("imap2.google2.com.", "*.google3.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com.", "a*.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com.", "ix*.google.com")); + assertTrue(verifyWithDomainNamePattern("imap.google.com.", "iMap.Google.Com")); + assertTrue(verifyWithDomainNamePattern("weird.", "weird")); + assertTrue(verifyWithDomainNamePattern("weird.", "weird.")); + + // Wildcards rejected for domain names consisting of fewer than two labels (excluding root). + assertFalse(verifyWithDomainNamePattern("weird.", "*weird")); + assertFalse(verifyWithDomainNamePattern("weird.", "weird*")); + assertFalse(verifyWithDomainNamePattern("weird.", "weird*.")); + assertFalse(verifyWithDomainNamePattern("weird.", "weird.*")); } public void testSubjectOnlyCert() throws Exception { @@ -168,8 +270,8 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "rs2oQLwOLnuifH52ey9+tJguabo+brlYYigAuWWFEzJfBzikDkIwnE/L7wlrypIk\n" + "taXDWI4=\n" + "-----END CERTIFICATE-----"); - assertTrue(verifier.verify("www.example.com", cert)); - assertFalse(verifier.verify("www2.example.com", cert)); + assertTrue(verifyWithServerCertificate("www.example.com", cert)); + assertFalse(verifyWithServerCertificate("www2.example.com", cert)); } public void testSubjectAltOnlyCert() throws Exception { @@ -192,8 +294,8 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "JPRynf9244Pn0Sr/wsnmdsTRFIFYynrc51hQ7DkwbUxpcaewkZzilru/SwZ3+pPT\n" + "9JSqm5hJ1pg5WDlPkW7c/1VA0/141N52Q8MIU+2ZpuOj\n" + "-----END CERTIFICATE-----"); - assertTrue(verifier.verify("www.example.com", cert)); - assertFalse(verifier.verify("www2.example.com", cert)); + assertTrue(verifyWithServerCertificate("www.example.com", cert)); + assertFalse(verifyWithServerCertificate("www2.example.com", cert)); } public void testSubjectWithAltNamesCert() throws Exception { @@ -219,10 +321,10 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "hrTVypLSoRXuTB2aWilu4p6aNh84xTdyqo2avtNr2MiQMZIcdamBq8LdBIAShFXI\n" + "h5G2eVGXH/Y=\n" + "-----END CERTIFICATE-----"); - assertFalse(verifier.verify("www.example.com", cert)); - assertTrue(verifier.verify("www2.example.com", cert)); - assertTrue(verifier.verify("www3.example.com", cert)); - assertFalse(verifier.verify("www4.example.com", cert)); + assertFalse(verifyWithServerCertificate("www.example.com", cert)); + assertTrue(verifyWithServerCertificate("www2.example.com", cert)); + assertTrue(verifyWithServerCertificate("www3.example.com", cert)); + assertFalse(verifyWithServerCertificate("www4.example.com", cert)); } public void testSubjectWithWildAltNamesCert() throws Exception { @@ -247,11 +349,11 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "Y3R0HZvKzNIU3pwAm69HCJoG+/9MZEIDJb0WJc5UygxDT45XE9zQMQe4dBOTaNXT\n" + "+ntgaB62kE10HzrzpqXAgoAWxWK4RzFcUpBWw9qYq9xOCewJ\n" + "-----END CERTIFICATE-----"); - assertFalse(verifier.verify("www.example.com", cert)); - assertFalse(verifier.verify("www2.example.com", cert)); - assertTrue(verifier.verify("www.example2.com", cert)); - assertTrue(verifier.verify("abc.example2.com", cert)); - assertFalse(verifier.verify("www.example3.com", cert)); + assertFalse(verifyWithServerCertificate("www.example.com", cert)); + assertFalse(verifyWithServerCertificate("www2.example.com", cert)); + assertTrue(verifyWithServerCertificate("www.example2.com", cert)); + assertTrue(verifyWithServerCertificate("abc.example2.com", cert)); + assertFalse(verifyWithServerCertificate("www.example3.com", cert)); } public void testWildAltNameOnlyCert() throws Exception { @@ -274,9 +376,9 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "va++ow5r1VxQXFJc0ZPzsDo+6TlktoDHaRQJGMqQomqHWT4i7F5UZgf6BHGfEUPU\n" + "qep+GsF3QRHSBtpObWkVDZNFvky3a1iZ2q25+hFIqQ==\n" + "-----END CERTIFICATE-----"); - assertTrue(verifier.verify("www.example.com", cert)); - assertTrue(verifier.verify("www2.example.com", cert)); - assertFalse(verifier.verify("www.example2.com", cert)); + assertTrue(verifyWithServerCertificate("www.example.com", cert)); + assertTrue(verifyWithServerCertificate("www2.example.com", cert)); + assertFalse(verifyWithServerCertificate("www.example2.com", cert)); } public void testAltIpOnlyCert() throws Exception { @@ -299,8 +401,48 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "WPjHQcWfpkFzAF5wyOq0kveVfx0g5xPhOVDd+U+q7WastbXICpCoHp9FxISmZVik\n" + "sAyifp8agkYdzaSh55fFmKXlFnRsQw==\n" + "-----END CERTIFICATE-----"); - assertTrue(verifier.verify("192.168.10.1", cert)); - assertFalse(verifier.verify("192.168.10.2", cert)); + assertTrue(verifyWithServerCertificate("192.168.10.1", cert)); + assertFalse(verifyWithServerCertificate("192.168.10.2", cert)); + } + + /** + * Verifies the provided hostname against the provided domain name pattern from server + * certificate. + */ + private boolean verifyWithDomainNamePattern(String hostname, String pattern) { + StubSSLSession session = new StubSSLSession(); + + // Verify using a certificate where the pattern is in the CN + session.peerCertificates = new Certificate[] { + new StubX509Certificate("cn=\"" + pattern + "\"") + }; + boolean resultWhenPatternInCn = verifier.verify(hostname, session); + + // Verify using a certificate where the pattern is in a DNS SubjectAltName + session.peerCertificates = new Certificate[] { + new StubX509Certificate("ou=test") + .addSubjectAlternativeName(ALT_DNS_NAME, pattern) + }; + boolean resultWhenPatternInSubjectAltName = verifier.verify(hostname, session); + + // Assert that in both cases the verifier gives the same result + if (resultWhenPatternInCn != resultWhenPatternInSubjectAltName) { + fail("Different results between pattern in CN and SubjectAltName." + + " hostname : " + hostname + ", pattern: " + pattern + + ", when pattern in CN: " + resultWhenPatternInCn + + ", when pattern in SubjectAltName: " + resultWhenPatternInSubjectAltName); + } + return resultWhenPatternInCn; + } + + /** + * Verifies the provided hostname against the provided server certificate. + */ + private boolean verifyWithServerCertificate(String hostname, X509Certificate certificate) { + StubSSLSession session = new StubSSLSession(); + session.peerCertificates = + (certificate != null) ? new Certificate[] {certificate} : new Certificate[0]; + return verifier.verify(hostname, session); } X509Certificate parseCertificate(String encoded) throws Exception { @@ -308,6 +450,117 @@ public final class DefaultHostnameVerifierTest extends TestCase { return (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(in); } + private static class StubSSLSession implements SSLSession { + + public Certificate[] peerCertificates = new Certificate[0]; + + @Override + public int getApplicationBufferSize() { + throw new UnsupportedOperationException(); + } + + @Override + public String getCipherSuite() { + throw new UnsupportedOperationException(); + } + + @Override + public long getCreationTime() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] getId() { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastAccessedTime() { + throw new UnsupportedOperationException(); + } + + @Override + public Certificate[] getLocalCertificates() { + throw new UnsupportedOperationException(); + } + + @Override + public Principal getLocalPrincipal() { + throw new UnsupportedOperationException(); + } + + @Override + public int getPacketBufferSize() { + throw new UnsupportedOperationException(); + } + + @Override + public javax.security.cert.X509Certificate[] getPeerCertificateChain() + throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException(); + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return peerCertificates; + } + + @Override + public String getPeerHost() { + throw new UnsupportedOperationException(); + } + + @Override + public int getPeerPort() { + throw new UnsupportedOperationException(); + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException(); + } + + @Override + public String getProtocol() { + throw new UnsupportedOperationException(); + } + + @Override + public SSLSessionContext getSessionContext() { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getValueNames() { + throw new UnsupportedOperationException(); + } + + @Override + public void invalidate() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public void putValue(String name, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeValue(String name) { + throw new UnsupportedOperationException(); + } + } + private static class StubX509Certificate extends X509Certificate { private final X500Principal subjectX500Principal; private Collection<List<?>> subjectAlternativeNames; diff --git a/luni/src/test/java/libcore/javax/net/ssl/HttpsURLConnectionTest.java b/luni/src/test/java/libcore/javax/net/ssl/HttpsURLConnectionTest.java new file mode 100644 index 0000000..cbaea20 --- /dev/null +++ b/luni/src/test/java/libcore/javax/net/ssl/HttpsURLConnectionTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.javax.net.ssl; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.net.URL; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; + +public class HttpsURLConnectionTest extends TestCase { + + /** + * HTTPS URL which cannot be resolved and is thus safe to use in tests where network traffic + * should be avoided. + */ + private static final String UNRESOLVABLE_HTTPS_URL = "https:///"; + + public void testDefaultHostnameVerifierNotNull() { + assertNotNull(HttpsURLConnection.getDefaultHostnameVerifier()); + } + + public void testDefaultHostnameVerifierUsedForNewConnectionsByDefault() throws IOException { + HostnameVerifier originalHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); + HttpsURLConnection connection = + (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(originalHostnameVerifier, connection.getHostnameVerifier()); + } finally { + connection.disconnect(); + } + + HostnameVerifier anotherVerifier = new FakeHostnameVerifier(); + try { + HttpsURLConnection.setDefaultHostnameVerifier(anotherVerifier); + connection = (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(anotherVerifier, connection.getHostnameVerifier()); + } finally { + connection.disconnect(); + } + + HttpsURLConnection.setDefaultHostnameVerifier(originalHostnameVerifier); + connection = (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(originalHostnameVerifier, connection.getHostnameVerifier()); + } finally { + connection.disconnect(); + } + } finally { + HttpsURLConnection.setDefaultHostnameVerifier(originalHostnameVerifier); + } + } + + public void testDefaultSSLSocketFactoryNotNull() { + assertNotNull(HttpsURLConnection.getDefaultSSLSocketFactory()); + } + + public void testDefaultSSLSocketFactoryUsedForNewConnectionsByDefault() throws IOException { + SSLSocketFactory originalFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); + HttpsURLConnection connection = + (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(originalFactory, connection.getSSLSocketFactory()); + } finally { + connection.disconnect(); + } + + SSLSocketFactory anotherFactory = new SSLSocketFactoryTest.FakeSSLSocketFactory(); + try { + HttpsURLConnection.setDefaultSSLSocketFactory(anotherFactory); + connection = (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(anotherFactory, connection.getSSLSocketFactory()); + } finally { + connection.disconnect(); + } + + HttpsURLConnection.setDefaultSSLSocketFactory(originalFactory); + connection = (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(originalFactory, connection.getSSLSocketFactory()); + } finally { + connection.disconnect(); + } + } finally { + HttpsURLConnection.setDefaultSSLSocketFactory(originalFactory); + } + } + + private static class FakeHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } +} diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLContextTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLContextTest.java index dccadbd..533849c 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLContextTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLContextTest.java @@ -26,7 +26,6 @@ import java.security.Security; import java.security.UnrecoverableKeyException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import libcore.io.IoUtils; @@ -82,14 +81,14 @@ public class SSLContextTest extends TestCase { } public void test_SSLContext_defaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLContext(SSLContext.getDefault()); + SSLConfigurationAsserts.assertSSLContextDefaultConfiguration(SSLContext.getDefault()); for (String protocol : StandardNames.SSL_CONTEXT_PROTOCOLS) { SSLContext sslContext = SSLContext.getInstance(protocol); if (!protocol.equals(StandardNames.SSL_CONTEXT_PROTOCOLS_DEFAULT)) { sslContext.init(null, null, null); } - SSLDefaultConfigurationAsserts.assertSSLContext(sslContext); + SSLConfigurationAsserts.assertSSLContextDefaultConfiguration(sslContext); } } @@ -149,6 +148,27 @@ public class SSLContextTest extends TestCase { sslContext); } + public void test_SSLContext_init_correctProtocolVersionsEnabled() throws Exception { + for (String tlsVersion : StandardNames.SSL_CONTEXT_PROTOCOLS) { + // Don't test the "Default" instance. + if (StandardNames.SSL_CONTEXT_PROTOCOLS_DEFAULT.equals(tlsVersion)) { + continue; + } + + SSLContext context = SSLContext.getInstance(tlsVersion); + context.init(null, null, null); + + StandardNames.assertSSLContextEnabledProtocols(tlsVersion, ((SSLSocket) (context.getSocketFactory() + .createSocket())).getEnabledProtocols()); + StandardNames.assertSSLContextEnabledProtocols(tlsVersion, ((SSLServerSocket) (context + .getServerSocketFactory().createServerSocket())).getEnabledProtocols()); + StandardNames.assertSSLContextEnabledProtocols(tlsVersion, context.getDefaultSSLParameters() + .getProtocols()); + StandardNames.assertSSLContextEnabledProtocols(tlsVersion, context.createSSLEngine() + .getEnabledProtocols()); + } + } + private static void assertEnabledCipherSuites( List<String> expectedCipherSuites, SSLContext sslContext) throws Exception { assertContentsInOrder( diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java index df4585f..5e3a3d5 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java @@ -65,7 +65,7 @@ public class SSLEngineTest extends TestCase { } public void test_SSLEngine_defaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLEngine( + SSLConfigurationAsserts.assertSSLEngineDefaultConfiguration( TestSSLContext.create().clientContext.createSSLEngine()); } @@ -203,24 +203,31 @@ public class SSLEngineTest extends TestCase { : new String[] { cipherSuite }); // Check that handshake succeeds. - TestSSLEnginePair pair = TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { - @Override - void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - client.setEnabledCipherSuites(cipherSuiteArray); - server.setEnabledCipherSuites(cipherSuiteArray); + TestSSLEnginePair pair = null; + try { + pair = TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + client.setEnabledCipherSuites(cipherSuiteArray); + server.setEnabledCipherSuites(cipherSuiteArray); + } + }); + assertConnected(pair); + + boolean needsRecordSplit = + ("TLS".equalsIgnoreCase(c.clientContext.getProtocol()) + || "SSLv3".equalsIgnoreCase(c.clientContext.getProtocol())) + && cipherSuite.contains("_CBC_"); + + assertSendsCorrectly("This is the client. Hello!".getBytes(), + pair.client, pair.server, needsRecordSplit); + assertSendsCorrectly("This is the server. Hi!".getBytes(), + pair.server, pair.client, needsRecordSplit); + } finally { + if (pair != null) { + pair.close(); } - }); - assertConnected(pair); - - boolean needsRecordSplit = - ("TLS".equalsIgnoreCase(c.clientContext.getProtocol()) - || "SSLv3".equalsIgnoreCase(c.clientContext.getProtocol())) - && cipherSuite.contains("_CBC_"); - - assertSendsCorrectly("This is the client. Hello!".getBytes(), - pair.client, pair.server, needsRecordSplit); - assertSendsCorrectly("This is the server. Hi!".getBytes(), - pair.server, pair.client, needsRecordSplit); + } // Check that handshake fails when the server does not possess the private key // corresponding to the server's certificate. This is achieved by using SSLContext @@ -234,17 +241,23 @@ public class SSLEngineTest extends TestCase { serverAuthenticatedUsingPublicKey = false; } if (serverAuthenticatedUsingPublicKey) { + TestSSLEnginePair p = null; try { - TestSSLEnginePair p = TestSSLEnginePair.create( + p = TestSSLEnginePair.create( cWithWrongPrivateKeys, new TestSSLEnginePair.Hooks() { - @Override + @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - client.setEnabledCipherSuites(cipherSuiteArray); - server.setEnabledCipherSuites(cipherSuiteArray); - } - }); + client.setEnabledCipherSuites(cipherSuiteArray); + server.setEnabledCipherSuites(cipherSuiteArray); + } + }); assertNotConnected(p); - } catch (IOException expected) {} + } catch (IOException expected) { + } finally { + if (p != null) { + p.close(); + } + } } } catch (Exception e) { String message = ("Problem trying to connect cipher suite " + cipherSuite); @@ -432,21 +445,28 @@ public class SSLEngineTest extends TestCase { fail(); } catch (IllegalStateException expected) { } + c.close(); - assertConnected(TestSSLEnginePair.create(null)); + TestSSLEnginePair p = TestSSLEnginePair.create(null); + assertConnected(p); + p.close(); - c.close(); } public void test_SSLEngine_beginHandshake_noKeyStore() throws Exception { TestSSLContext c = TestSSLContext.create(null, null, null, null, null, null, null, null, SSLContext.getDefault(), SSLContext.getDefault()); + SSLEngine[] p = null; try { // TODO Fix KnownFailure AlertException "NO SERVER CERTIFICATE FOUND" // ServerHandshakeImpl.selectSuite should not select a suite without a required cert - TestSSLEnginePair.connect(c, null); + p = TestSSLEnginePair.connect(c, null); fail(); } catch (SSLHandshakeException expected) { + } finally { + if (p != null) { + TestSSLEnginePair.close(p); + } } c.close(); } @@ -456,6 +476,7 @@ public class SSLEngineTest extends TestCase { SSLEngine[] engines = TestSSLEnginePair.connect(c, null); assertConnected(engines[0], engines[1]); c.close(); + TestSSLEnginePair.close(engines); } public void test_SSLEngine_getUseClientMode() throws Exception { @@ -467,33 +488,47 @@ public class SSLEngineTest extends TestCase { public void test_SSLEngine_setUseClientMode() throws Exception { boolean[] finished; + TestSSLEnginePair p = null; // client is client, server is server finished = new boolean[2]; - assertConnected(test_SSLEngine_setUseClientMode(true, false, finished)); + p = test_SSLEngine_setUseClientMode(true, false, finished); + assertConnected(p); assertTrue(finished[0]); assertTrue(finished[1]); + p.close(); // client is server, server is client finished = new boolean[2]; - assertConnected(test_SSLEngine_setUseClientMode(false, true, finished)); + p = test_SSLEngine_setUseClientMode(false, true, finished); + assertConnected(p); assertTrue(finished[0]); assertTrue(finished[1]); + p.close(); // both are client /* * Our implementation throws an SSLHandshakeException, but RI just * stalls forever */ + p = null; try { - assertNotConnected(test_SSLEngine_setUseClientMode(true, true, null)); + p = test_SSLEngine_setUseClientMode(true, true, null); + assertNotConnected(p); assertTrue(StandardNames.IS_RI); } catch (SSLHandshakeException maybeExpected) { assertFalse(StandardNames.IS_RI); + } finally { + if (p != null) { + p.close(); + } + } + p = test_SSLEngine_setUseClientMode(false, false, null); // both are server - assertNotConnected(test_SSLEngine_setUseClientMode(false, false, null)); + assertNotConnected(p); + p.close(); } public void test_SSLEngine_setUseClientMode_afterHandshake() throws Exception { @@ -510,6 +545,7 @@ public class SSLEngineTest extends TestCase { fail(); } catch (IllegalArgumentException expected) { } + pair.close(); } private TestSSLEnginePair test_SSLEngine_setUseClientMode(final boolean clientClientMode, @@ -572,6 +608,7 @@ public class SSLEngineTest extends TestCase { p.client.getSession().getLocalCertificates()); clientAuthContext.close(); c.close(); + p.close(); } /** @@ -591,6 +628,7 @@ public class SSLEngineTest extends TestCase { }); assertConnected(p); clientAuthContext.close(); + p.close(); } /** @@ -604,8 +642,9 @@ public class SSLEngineTest extends TestCase { TestSSLContext clientAuthContext = TestSSLContext.create(TestKeyStore.getClient(), TestKeyStore.getServer()); + TestSSLEnginePair p = null; try { - TestSSLEnginePair.create(clientAuthContext, + p = TestSSLEnginePair.create(clientAuthContext, new TestSSLEnginePair.Hooks() { @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { @@ -616,6 +655,9 @@ public class SSLEngineTest extends TestCase { } catch (SSLHandshakeException expected) { } finally { clientAuthContext.close(); + if (p != null) { + p.close(); + } } } @@ -624,11 +666,13 @@ public class SSLEngineTest extends TestCase { SSLEngine e = c.clientContext.createSSLEngine(); assertTrue(e.getEnableSessionCreation()); c.close(); + TestSSLEnginePair.close(new SSLEngine[] { e }); } public void test_SSLEngine_setEnableSessionCreation_server() throws Exception { + TestSSLEnginePair p = null; try { - TestSSLEnginePair p = TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() { + p = TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() { @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { server.setEnableSessionCreation(false); @@ -639,12 +683,17 @@ public class SSLEngineTest extends TestCase { assertNotConnected(p); } catch (SSLException maybeExpected) { assertFalse(StandardNames.IS_RI); + } finally { + if (p != null) { + p.close(); + } } } public void test_SSLEngine_setEnableSessionCreation_client() throws Exception { + TestSSLEnginePair p = null; try { - TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() { + p = TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() { @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { client.setEnableSessionCreation(false); @@ -652,6 +701,10 @@ public class SSLEngineTest extends TestCase { }); fail(); } catch (SSLException expected) { + } finally { + if (p != null) { + p.close(); + } } } @@ -735,5 +788,6 @@ public class SSLEngineTest extends TestCase { assertNotNull(test.server); assertNotNull(test.client); assertConnected(test); + test.close(); } } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketFactoryTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketFactoryTest.java index ea9c3f0..cda1fb8 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketFactoryTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketFactoryTest.java @@ -22,7 +22,7 @@ import junit.framework.TestCase; public class SSLServerSocketFactoryTest extends TestCase { public void testDefaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLServerSocketFactory( + SSLConfigurationAsserts.assertSSLServerSocketFactoryDefaultConfiguration( (SSLServerSocketFactory) SSLServerSocketFactory.getDefault()); } } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketTest.java index 59c44c1..d2c0f48 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketTest.java @@ -24,7 +24,7 @@ import java.util.Arrays; public class SSLServerSocketTest extends TestCase { public void testDefaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLServerSocket( + SSLConfigurationAsserts.assertSSLServerSocketDefaultConfiguration( (SSLServerSocket) SSLServerSocketFactory.getDefault().createServerSocket()); } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketFactoryTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketFactoryTest.java index acf69c0..83b690b 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketFactoryTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketFactoryTest.java @@ -210,7 +210,7 @@ public class SSLSocketFactoryTest extends TestCase { } public void test_SSLSocketFactory_defaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLSocketFactory( + SSLConfigurationAsserts.assertSSLSocketFactoryDefaultConfiguration( (SSLSocketFactory) SSLSocketFactory.getDefault()); } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java index 4af7f5a..8e4519d 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java @@ -16,6 +16,8 @@ package libcore.javax.net.ssl; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -26,15 +28,18 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -66,11 +71,23 @@ import libcore.io.IoUtils; import libcore.io.Streams; import libcore.java.security.StandardNames; import libcore.java.security.TestKeyStore; +import libcore.tlswire.handshake.CipherSuite; +import libcore.tlswire.handshake.ClientHello; +import libcore.tlswire.handshake.CompressionMethod; +import libcore.tlswire.handshake.HandshakeMessage; +import libcore.tlswire.handshake.HelloExtension; +import libcore.tlswire.handshake.ServerNameHelloExtension; +import libcore.tlswire.record.TlsProtocols; +import libcore.tlswire.record.TlsRecord; +import libcore.tlswire.util.TlsProtocolVersion; +import tests.util.ForEachRunner; +import tests.util.DelegatingSSLSocketFactory; +import tests.util.Pair; public class SSLSocketTest extends TestCase { public void test_SSLSocket_defaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLSocket( + SSLConfigurationAsserts.assertSSLSocketDefaultConfiguration( (SSLSocket) SSLSocketFactory.getDefault().createSocket()); } @@ -1458,11 +1475,147 @@ public class SSLSocketTest extends TestCase { test.close(); } - public void test_SSLSocket_ClientHello_size() throws Exception { + public void test_SSLSocket_ClientHello_record_size() throws Exception { // This test checks the size of ClientHello of the default SSLSocket. TLS/SSL handshakes // with older/unpatched F5/BIG-IP appliances are known to stall and time out when // the fragment containing ClientHello is between 256 and 511 (inclusive) bytes long. - // + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, null, null); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + sslSocketFactory = new DelegatingSSLSocketFactory(sslSocketFactory) { + @Override + protected void configureSocket(SSLSocket socket) { + // Enable SNI extension on the socket (this is typically enabled by default) + // to increase the size of ClientHello. + try { + Method setHostname = + socket.getClass().getMethod("setHostname", String.class); + setHostname.invoke(socket, "sslsockettest.androidcts.google.com"); + } catch (NoSuchMethodException ignored) { + } catch (Exception e) { + throw new RuntimeException("Failed to enable SNI", e); + } + + // Enable Session Tickets extension on the socket (this is typically enabled + // by default) to increase the size of ClientHello. + try { + Method setUseSessionTickets = + socket.getClass().getMethod( + "setUseSessionTickets", boolean.class); + setUseSessionTickets.invoke(socket, true); + } catch (NoSuchMethodException ignored) { + } catch (Exception e) { + throw new RuntimeException("Failed to enable Session Tickets", e); + } + } + }; + + TlsRecord firstReceivedTlsRecord = captureTlsHandshakeFirstTlsRecord(sslSocketFactory); + assertEquals("TLS record type", TlsProtocols.HANDSHAKE, firstReceivedTlsRecord.type); + HandshakeMessage handshakeMessage = HandshakeMessage.read( + new DataInputStream(new ByteArrayInputStream(firstReceivedTlsRecord.fragment))); + assertEquals("HandshakeMessage type", + HandshakeMessage.TYPE_CLIENT_HELLO, handshakeMessage.type); + int fragmentLength = firstReceivedTlsRecord.fragment.length; + if ((fragmentLength >= 256) && (fragmentLength <= 511)) { + fail("Fragment containing ClientHello is of dangerous length: " + + fragmentLength + " bytes"); + } + } + + public void test_SSLSocket_ClientHello_cipherSuites() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + String[] cipherSuites = new String[clientHello.cipherSuites.size()]; + for (int i = 0; i < clientHello.cipherSuites.size(); i++) { + CipherSuite cipherSuite = clientHello.cipherSuites.get(i); + cipherSuites[i] = cipherSuite.getAndroidName(); + } + StandardNames.assertDefaultCipherSuites(cipherSuites); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_clientProtocolVersion() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + assertEquals(TlsProtocolVersion.TLSv1_2, clientHello.clientVersion); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_compressionMethods() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + assertEquals(Arrays.asList(CompressionMethod.NULL), clientHello.compressionMethods); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_SNI() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + ServerNameHelloExtension sniExtension = (ServerNameHelloExtension) + clientHello.findExtensionByType(HelloExtension.TYPE_SERVER_NAME); + assertNotNull(sniExtension); + assertEquals(Arrays.asList("localhost.localdomain"), sniExtension.hostnames); + } + }, getSSLSocketFactoriesToTest()); + } + + private List<Pair<String, SSLSocketFactory>> getSSLSocketFactoriesToTest() + throws NoSuchAlgorithmException, KeyManagementException { + List<Pair<String, SSLSocketFactory>> result = + new ArrayList<Pair<String, SSLSocketFactory>>(); + result.add(Pair.of("default", (SSLSocketFactory) SSLSocketFactory.getDefault())); + for (String sslContextProtocol : StandardNames.SSL_CONTEXT_PROTOCOLS) { + SSLContext sslContext = SSLContext.getInstance(sslContextProtocol); + if (StandardNames.SSL_CONTEXT_PROTOCOLS_DEFAULT.equals(sslContextProtocol)) { + continue; + } + sslContext.init(null, null, null); + result.add(Pair.of( + "SSLContext(\"" + sslContext.getProtocol() + "\")", + sslContext.getSocketFactory())); + } + return result; + } + + private ClientHello captureTlsHandshakeClientHello(SSLSocketFactory sslSocketFactory) + throws Exception { + TlsRecord record = captureTlsHandshakeFirstTlsRecord(sslSocketFactory); + assertEquals("TLS record type", TlsProtocols.HANDSHAKE, record.type); + ByteArrayInputStream fragmentIn = new ByteArrayInputStream(record.fragment); + HandshakeMessage handshakeMessage = HandshakeMessage.read(new DataInputStream(fragmentIn)); + assertEquals("HandshakeMessage type", + HandshakeMessage.TYPE_CLIENT_HELLO, handshakeMessage.type); + // Assert that the fragment does not contain any more messages + assertEquals(0, fragmentIn.available()); + + return (ClientHello) handshakeMessage; + } + + private TlsRecord captureTlsHandshakeFirstTlsRecord(SSLSocketFactory sslSocketFactory) + throws Exception { + byte[] firstReceivedChunk = captureTlsHandshakeFirstTransmittedChunkBytes(sslSocketFactory); + ByteArrayInputStream firstReceivedChunkIn = new ByteArrayInputStream(firstReceivedChunk); + TlsRecord record = TlsRecord.read(new DataInputStream(firstReceivedChunkIn)); + // Assert that the chunk does not contain any more data + assertEquals(0, firstReceivedChunkIn.available()); + + return record; + } + + private byte[] captureTlsHandshakeFirstTransmittedChunkBytes( + final SSLSocketFactory sslSocketFactory) throws Exception { // Since there's no straightforward way to obtain a ClientHello from SSLSocket, this test // does the following: // 1. Creates a listening server socket (a plain one rather than a TLS/SSL one). @@ -1506,33 +1659,19 @@ public class SSLSocketTest extends TestCase { executorService.submit(new Callable<Void>() { @Override public Void call() throws Exception { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, null, null); - SSLSocket client = (SSLSocket) sslContext.getSocketFactory().createSocket(); + Socket client = new Socket(); sockets[0] = client; try { - // Enable SNI extension on the socket (this is typically enabled by default) - // to increase the size of ClientHello. - try { - Method setHostname = - client.getClass().getMethod("setHostname", String.class); - setHostname.invoke(client, "sslsockettest.androidcts.google.com"); - } catch (NoSuchMethodException ignored) {} - - // Enable Session Tickets extension on the socket (this is typically enabled - // by default) to increase the size of ClientHello. - try { - Method setUseSessionTickets = - client.getClass().getMethod( - "setUseSessionTickets", boolean.class); - setUseSessionTickets.invoke(client, true); - } catch (NoSuchMethodException ignored) {} - client.connect(finalListeningSocket.getLocalSocketAddress()); // Initiate the TLS/SSL handshake which is expected to fail as soon as the // server socket receives a ClientHello. try { - client.startHandshake(); + SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( + client, + "localhost.localdomain", + finalListeningSocket.getLocalPort(), + true); + sslSocket.startHandshake(); fail(); return null; } catch (IOException expected) {} @@ -1551,16 +1690,7 @@ public class SSLSocketTest extends TestCase { }); // Wait for the ClientHello to arrive - byte[] clientHello = readFirstReceivedChunkFuture.get(10, TimeUnit.SECONDS); - - // Check for ClientHello length that may cause handshake to fail/time out with older - // F5/BIG-IP appliances. - assertEquals("TLS record type: handshake", 22, clientHello[0]); - int fragmentLength = ((clientHello[3] & 0xff) << 8) | (clientHello[4] & 0xff); - if ((fragmentLength >= 256) && (fragmentLength <= 511)) { - fail("Fragment containing ClientHello is of dangerous length: " - + fragmentLength + " bytes"); - } + return readFirstReceivedChunkFuture.get(10, TimeUnit.SECONDS); } finally { executorService.shutdownNow(); IoUtils.closeQuietly(listeningSocket); diff --git a/luni/src/test/java/libcore/net/MimeUtilsTest.java b/luni/src/test/java/libcore/net/MimeUtilsTest.java index 9bfb375..ff22632 100644 --- a/luni/src/test/java/libcore/net/MimeUtilsTest.java +++ b/luni/src/test/java/libcore/net/MimeUtilsTest.java @@ -27,6 +27,12 @@ public class MimeUtilsTest extends TestCase { assertEquals("flac", MimeUtils.guessExtensionFromMimeType("application/x-flac")); } + // https://code.google.com/p/android/issues/detail?id=78909 + public void test_78909() { + assertEquals("mka", MimeUtils.guessExtensionFromMimeType("audio/x-matroska")); + assertEquals("mkv", MimeUtils.guessExtensionFromMimeType("video/x-matroska")); + } + public void test_16978217() { assertEquals("image/x-ms-bmp", MimeUtils.guessMimeTypeFromExtension("bmp")); assertEquals("image/x-icon", MimeUtils.guessMimeTypeFromExtension("ico")); diff --git a/luni/src/test/java/libcore/net/NetworkSecurityPolicyTest.java b/luni/src/test/java/libcore/net/NetworkSecurityPolicyTest.java new file mode 100644 index 0000000..d04e2ba --- /dev/null +++ b/luni/src/test/java/libcore/net/NetworkSecurityPolicyTest.java @@ -0,0 +1,315 @@ +/* + * 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.net; + +import junit.framework.TestCase; +import libcore.io.IoUtils; +import java.io.Closeable; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.ErrorManager; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.SocketHandler; + +public class NetworkSecurityPolicyTest extends TestCase { + + private boolean mCleartextTrafficPermittedOriginalState; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mCleartextTrafficPermittedOriginalState = + NetworkSecurityPolicy.isCleartextTrafficPermitted(); + } + + @Override + protected void tearDown() throws Exception { + try { + NetworkSecurityPolicy.setCleartextTrafficPermitted( + mCleartextTrafficPermittedOriginalState); + } finally { + super.tearDown(); + } + } + + public void testCleartextTrafficPolicySetterAndGetter() { + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + assertEquals(false, NetworkSecurityPolicy.isCleartextTrafficPermitted()); + + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + assertEquals(true, NetworkSecurityPolicy.isCleartextTrafficPermitted()); + + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + assertEquals(false, NetworkSecurityPolicy.isCleartextTrafficPermitted()); + + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + assertEquals(true, NetworkSecurityPolicy.isCleartextTrafficPermitted()); + } + + public void testCleartextTrafficPolicyWithHttpURLConnection() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + try (CapturingServerSocket server = new CapturingServerSocket()) { + URL url = new URL("http://localhost:" + server.getPort() + "/test.txt"); + try { + url.openConnection().getContent(); + fail(); + } catch (IOException expected) { + } + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted and + // that URLConnection.openConnection or getContent fail with an IOException. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket()) { + URL url = new URL("http://localhost:" + server.getPort() + "/test.txt"); + try { + url.openConnection().getContent(); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + public void testCleartextTrafficPolicyWithFtpURLConnection() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + byte[] serverReplyOnConnect = "220\r\n".getBytes("US-ASCII"); + try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { + URL url = new URL("ftp://localhost:" + server.getPort() + "/test.txt"); + try { + url.openConnection().getContent(); + fail(); + } catch (IOException expected) { + } + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted and + // that URLConnection.openConnection or getContent fail with an IOException. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { + URL url = new URL("ftp://localhost:" + server.getPort() + "/test.txt"); + try { + url.openConnection().getContent(); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + public void testCleartextTrafficPolicyWithJarHttpURLConnection() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + try (CapturingServerSocket server = new CapturingServerSocket()) { + URL url = new URL("jar:http://localhost:" + server.getPort() + "/test.jar!/"); + try { + ((JarURLConnection) url.openConnection()).getManifest(); + fail(); + } catch (IOException expected) { + } + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted and + // that JarURLConnection.openConnection or getManifest fail with an IOException. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket()) { + URL url = new URL("jar:http://localhost:" + server.getPort() + "/test.jar!/"); + try { + ((JarURLConnection) url.openConnection()).getManifest(); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + public void testCleartextTrafficPolicyWithJarFtpURLConnection() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + byte[] serverReplyOnConnect = "220\r\n".getBytes("US-ASCII"); + try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { + URL url = new URL("jar:ftp://localhost:" + server.getPort() + "/test.jar!/"); + try { + ((JarURLConnection) url.openConnection()).getManifest(); + fail(); + } catch (IOException expected) { + } + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted and + // that JarURLConnection.openConnection or getManifest fail with an IOException. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { + URL url = new URL("jar:ftp://localhost:" + server.getPort() + "/test.jar!/"); + try { + ((JarURLConnection) url.openConnection()).getManifest(); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + public void testCleartextTrafficPolicyWithLoggingSocketHandler() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + try (CapturingServerSocket server = new CapturingServerSocket()) { + SocketHandler logger = new SocketHandler("localhost", server.getPort()); + MockErrorManager mockErrorManager = new MockErrorManager(); + logger.setErrorManager(mockErrorManager); + logger.setLevel(Level.ALL); + LogRecord record = new LogRecord(Level.INFO, "A log record"); + assertTrue(logger.isLoggable(record)); + logger.publish(record); + assertNull(mockErrorManager.getMostRecentException()); + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket()) { + try { + new SocketHandler("localhost", server.getPort()); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + /** + * Server socket which listens on a local port and captures the first chunk of data transmitted + * by the client. + */ + private static class CapturingServerSocket implements Closeable { + private final ServerSocket mSocket; + private final int mPort; + private final Thread mListeningThread; + private final FutureTask<byte[]> mFirstChunkReceivedFuture; + + /** + * Constructs a new socket listening on a local port. + */ + public CapturingServerSocket() throws IOException { + this(null); + } + + /** + * Constructs a new socket listening on a local port, which sends the provided reply as + * soon as a client connects to it. + */ + public CapturingServerSocket(final byte[] replyOnConnect) throws IOException { + mSocket = new ServerSocket(0); + mPort = mSocket.getLocalPort(); + mFirstChunkReceivedFuture = new FutureTask<byte[]>(new Callable<byte[]>() { + @Override + public byte[] call() throws Exception { + try (Socket client = mSocket.accept()) { + // Reply (if requested) + if (replyOnConnect != null) { + client.getOutputStream().write(replyOnConnect); + client.getOutputStream().flush(); + } + + // Read request + byte[] buf = new byte[64 * 1024]; + int chunkSize = client.getInputStream().read(buf); + if (chunkSize == -1) { + // Connection closed without any data received + return new byte[0]; + } + // Received some data + return Arrays.copyOf(buf, chunkSize); + } finally { + IoUtils.closeQuietly(mSocket); + } + } + }); + mListeningThread = new Thread(mFirstChunkReceivedFuture); + mListeningThread.start(); + } + + public int getPort() { + return mPort; + } + + public Future<byte[]> getFirstReceivedChunkFuture() { + return mFirstChunkReceivedFuture; + } + + @Override + public void close() { + IoUtils.closeQuietly(mSocket); + mListeningThread.interrupt(); + } + + private void assertDataTransmittedByClient() + throws Exception { + byte[] firstChunkFromClient = getFirstReceivedChunkFuture().get(2, TimeUnit.SECONDS); + if ((firstChunkFromClient == null) || (firstChunkFromClient.length == 0)) { + fail("Client did not transmit any data to server"); + } + } + + private void assertNoDataTransmittedByClient() + throws Exception { + byte[] firstChunkFromClient; + try { + firstChunkFromClient = getFirstReceivedChunkFuture().get(2, TimeUnit.SECONDS); + } catch (TimeoutException expected) { + return; + } + if ((firstChunkFromClient != null) && (firstChunkFromClient.length > 0)) { + fail("Client transmitted " + firstChunkFromClient.length+ " bytes: " + + new String(firstChunkFromClient, "US-ASCII")); + } + } + } + + private static class MockErrorManager extends ErrorManager { + private Exception mMostRecentException; + + public Exception getMostRecentException() { + synchronized (this) { + return mMostRecentException; + } + } + + @Override + public void error(String message, Exception exception, int errorCode) { + synchronized (this) { + mMostRecentException = exception; + } + } + } +} diff --git a/luni/src/test/java/libcore/net/http/ResponseUtilsTest.java b/luni/src/test/java/libcore/net/http/ResponseUtilsTest.java new file mode 100644 index 0000000..16745ee --- /dev/null +++ b/luni/src/test/java/libcore/net/http/ResponseUtilsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 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.net.http; + +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import junit.framework.TestCase; +import static libcore.net.http.ResponseUtils.responseCharset; + +public class ResponseUtilsTest extends TestCase { + public void test_responseCharset_missing() { + assertEquals(StandardCharsets.UTF_8, responseCharset(null)); + assertEquals(StandardCharsets.UTF_8, responseCharset("text/plain")); + assertEquals(StandardCharsets.UTF_8, responseCharset("text/plain;foo=bar;baz=bal")); + assertEquals(StandardCharsets.UTF_8, responseCharset("text/plain;charset=")); + } + + public void test_responseCharset_valid() { + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain;charset=ISO-8859-1")); + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain;CHARSET=ISO-8859-1")); + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain; charset = ISO-8859-1")); + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain; foo=bar;baz=bag;charset=ISO-8859-1")); + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain;charset=ISO-8859-1;;==,==")); + } + + public void test_responseCharset_invalid() { + try { + responseCharset("text/plain;charset=unsupportedCharset"); + fail(); + } catch (UnsupportedCharsetException expected) { + } + } +} diff --git a/luni/src/test/java/libcore/util/HexEncodingTest.java b/luni/src/test/java/libcore/util/HexEncodingTest.java new file mode 100644 index 0000000..ef79f5c --- /dev/null +++ b/luni/src/test/java/libcore/util/HexEncodingTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.util; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import junit.framework.TestCase; +import static libcore.util.HexEncoding.decode; +import static libcore.util.HexEncoding.encode; + +public class HexEncodingTest extends TestCase { + public void testEncode() { + final byte[] avocados = "avocados".getBytes(StandardCharsets.UTF_8); + + assertArraysEqual("61766f6361646f73".toCharArray(), encode(avocados)); + assertArraysEqual(avocados, decode(encode(avocados), false)); + } + + public void testDecode_allow4Bit() { + assertArraysEqual(new byte[] { 6 }, decode("6".toCharArray(), true)); + assertArraysEqual(new byte[] { 6, 'v' }, decode("676".toCharArray(), true)); + } + + public void testDecode_disallow4Bit() { + try { + decode("676".toCharArray(), false); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testDecode_invalid() { + try { + decode("deadbard".toCharArray(), false); + fail(); + } catch (IllegalArgumentException expected) { + } + + // This demonstrates a difference in behaviour from apache commons : apache + // commons uses Character.isDigit and would successfully decode a string with + // arabic and devanagari characters. + try { + decode("६१٧٥٥f6361646f73".toCharArray(), false); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + decode("#%6361646f73".toCharArray(), false); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + private static void assertArraysEqual(char[] lhs, char[] rhs) { + assertEquals(new String(lhs), new String(rhs)); + } + + private static void assertArraysEqual(byte[] lhs, byte[] rhs) { + assertEquals(Arrays.toString(lhs), Arrays.toString(rhs)); + } +} diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/ExemptionMechanismTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/ExemptionMechanismTest.java index 87b2913..17fb127 100644 --- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/ExemptionMechanismTest.java +++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/ExemptionMechanismTest.java @@ -28,7 +28,6 @@ import java.security.Provider; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; -import java.util.Vector; import javax.crypto.ExemptionMechanism; import javax.crypto.ExemptionMechanismException; @@ -37,7 +36,6 @@ import javax.crypto.KeyGenerator; import javax.crypto.ShortBufferException; import org.apache.harmony.crypto.tests.support.MyExemptionMechanismSpi; -import org.apache.harmony.crypto.tests.support.MyExemptionMechanismSpi.tmpKey; import org.apache.harmony.security.tests.support.SpiEngUtils; import junit.framework.TestCase; @@ -170,45 +168,6 @@ public class ExemptionMechanismTest extends TestCase { em.genExemptionBlob(new byte[10], -5); } - static boolean flag = false; - - class Mock_ExemptionMechanism extends ExemptionMechanism { - protected Mock_ExemptionMechanism(ExemptionMechanismSpi exmechSpi, Provider provider, String mechanism) { - super(exmechSpi, provider, mechanism); - } - - @Override - protected void finalize() { - flag = true; - super.finalize(); - } - } - - // Side Effect: Causes OutOfMemoryError to test finalization - public void test_finalize () { - Mock_ExemptionMechanism mem = new Mock_ExemptionMechanism(null, null, "Name"); - assertNotNull(mem); - mem = null; - assertFalse(flag); - Vector v = new Vector(); - int capacity; - try { - while(true) { - v.add(this); - } - } catch (OutOfMemoryError e) { - capacity = v.size(); - v = null; - } - - v = new Vector(); - for (int i = 0; i < capacity/2; i++) { - v.add(this); - } - v = null; - assertTrue(flag); - } - class Mock_ExemptionMechanismSpi extends MyExemptionMechanismSpi { @Override protected byte[] engineGenExemptionBlob() diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java index ddd0695..e90452d 100644 --- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java +++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java @@ -100,6 +100,12 @@ public class MacTest extends TestCase { macList.add(Mac.getInstance(defaultAlgorithm, defaultProvider)); macList.add(Mac.getInstance(defaultAlgorithm, defaultProviderName)); for (Provider p : Security.getProviders("Mac." + defaultAlgorithm)) { + // Do not test AndroidKeyStore's Mac. It cannot be initialized without providing an + // AndroidKeyStore-backed SecretKey instance. It's OKish not to test here because it's + // tested by cts/tests/test/keystore. + if ("AndroidKeyStore".equals(p.getName())) { + continue; + } macList.add(Mac.getInstance(defaultAlgorithm, p)); } return macList.toArray(new Mac[macList.size()]); @@ -845,6 +851,13 @@ public class MacTest extends TestCase { byte[] output = null; byte[] output2 = null; for (int i = 0; i < providers.length; i++) { + // Do not test AndroidKeyStore's Mac. It cannot be initialized without providing an + // AndroidKeyStore-backed SecretKey instance. It's OKish not to test here because it's + // tested by cts/tests/test/keystore. + if ("AndroidKeyStore".equals(providers[i].getName())) { + continue; + } + System.out.println("provider = " + providers[i].getName()); Mac mac = Mac.getInstance("HmacMD5", providers[i]); mac.init(key); @@ -884,6 +897,24 @@ public class MacTest extends TestCase { public abstract void setup(); } + public void testMac_getInstance_DoesNotSupportKeyClass_Success() throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Mac.FOO", MockMacSpi.AllKeyTypes.class.getName()); + put("Mac.FOO SupportedKeyClasses", "None"); + } + }; + + Security.addProvider(mockProvider); + try { + Mac s = Mac.getInstance("FOO", mockProvider); + s.init(new MockKey()); + assertEquals(mockProvider, s.getProvider()); + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + public void testMac_getInstance_SuppliedProviderNotRegistered_Success() throws Exception { Provider mockProvider = new MockProvider("MockProvider") { public void setup() { diff --git a/luni/src/test/native/libcore_io_Memory_test.cpp b/luni/src/test/native/libcore_io_Memory_test.cpp new file mode 100644 index 0000000..2d95155 --- /dev/null +++ b/luni/src/test/native/libcore_io_Memory_test.cpp @@ -0,0 +1,134 @@ +/* + * 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. + */ + +#include "luni/src/main/native/libcore_io_Memory.cpp" + +#include <stdlib.h> + +#include <functional> + +#include <gtest/gtest.h> + +#define ALIGNMENT 8 + +template<typename T, size_t NUM_ELEMENTS> +void swap_align_test(void (*swap_func)(T*, const T*, size_t), + std::function<void (T*, T*, uint64_t)> init_func) { + uint8_t* dst = nullptr; + uint8_t* src = nullptr; + ASSERT_EQ(0, posix_memalign(reinterpret_cast<void**>(&dst), ALIGNMENT, + sizeof(T) * NUM_ELEMENTS + ALIGNMENT)); + ASSERT_EQ(0, posix_memalign(reinterpret_cast<void**>(&src), ALIGNMENT, + sizeof(T) * NUM_ELEMENTS + ALIGNMENT)); + + T src_buf[NUM_ELEMENTS]; + T dst_buf[NUM_ELEMENTS]; + for (uint64_t i = 0; i < NUM_ELEMENTS; i++) { + init_func(&src_buf[i], &dst_buf[i], i); + } + + // Vary a few alignments. + for (size_t dst_align = 0; dst_align < ALIGNMENT; dst_align++) { + T* dst_aligned = reinterpret_cast<T*>(&dst[dst_align]); + for (size_t src_align = 0; src_align < ALIGNMENT; src_align++) { + T* src_aligned = reinterpret_cast<T*>(&src[src_align]); + memset(dst_aligned, 0, sizeof(T) * NUM_ELEMENTS); + memcpy(src_aligned, src_buf, sizeof(T) * NUM_ELEMENTS); + swap_func(dst_aligned, src_aligned, NUM_ELEMENTS); + ASSERT_EQ(0, memcmp(dst_buf, dst_aligned, sizeof(T) * NUM_ELEMENTS)) + << "Failed at dst align " << dst_align << " src align " << src_align; + } + } + free(dst); + free(src); +} + +TEST(libcore, swapShorts_align_test) { + // Use an odd number to guarantee that the last 16-bit swap code + // is executed. + swap_align_test<jshort, 9> (swapShorts, [] (jshort* src, jshort* dst, uint64_t i) { + *src = ((2*i) << 8) | (2*(i+1)); + *dst = (2*i) | ((2*(i+1)) << 8); + }); +} + +TEST(libcore, swapInts_align_test) { + swap_align_test<jint, 10> (swapInts, [] (jint* src, jint* dst, uint64_t i) { + *src = ((4*i) << 24) | ((4*(i+1)) << 16) | ((4*(i+2)) << 8) | (4*(i+3)); + *dst = (4*i) | ((4*(i+1)) << 8) | ((4*(i+2)) << 16) | ((4*(i+3)) << 24); + }); +} + +TEST(libcore, swapLongs_align_test) { + swap_align_test<jlong, 10> (swapLongs, [] (jlong* src, jlong* dst, uint64_t i) { + *src = ((8*i) << 56) | ((8*(i+1)) << 48) | ((8*(i+2)) << 40) | ((8*(i+3)) << 32) | + ((8*(i+4)) << 24) | ((8*(i+5)) << 16) | ((8*(i+6)) << 8) | (8*(i+7)); + *dst = (8*i) | ((8*(i+1)) << 8) | ((8*(i+2)) << 16) | ((8*(i+3)) << 24) | + ((8*(i+4)) << 32) | ((8*(i+5)) << 40) | ((8*(i+6)) << 48) | ((8*(i+7)) << 56); + }); +} + +template<typename T> +void memory_peek_test(T (*peek_func)(JNIEnv*, jclass, jlong), T value) { + T* src = nullptr; + ASSERT_EQ(0, posix_memalign(reinterpret_cast<void**>(&src), ALIGNMENT, + sizeof(T) + ALIGNMENT)); + for (size_t i = 0; i < ALIGNMENT; i++) { + jlong src_aligned = reinterpret_cast<jlong>(src) + i; + memcpy(reinterpret_cast<void*>(src_aligned), &value, sizeof(T)); + T result = peek_func(nullptr, nullptr, src_aligned); + ASSERT_EQ(value, result); + } + free(src); +} + +TEST(libcore, Memory_peekShortNative_align_check) { + memory_peek_test<jshort>(Memory_peekShortNative, 0x0102); +} + +TEST(libcore, Memory_peekIntNative_align_check) { + memory_peek_test<jint>(Memory_peekIntNative, 0x01020304); +} + +TEST(libcore, Memory_peekLongNative_align_check) { + memory_peek_test<jlong>(Memory_peekLongNative, 0x01020405060708ULL); +} + +template<typename T> +void memory_poke_test(void (*poke_func)(JNIEnv*, jclass, jlong, T), T value) { + T* dst = nullptr; + ASSERT_EQ(0, posix_memalign(reinterpret_cast<void**>(&dst), ALIGNMENT, + sizeof(T) + ALIGNMENT)); + for(size_t i = 0; i < ALIGNMENT; i++) { + memset(dst, 0, sizeof(T) + ALIGNMENT); + jlong dst_aligned = reinterpret_cast<jlong>(dst) + i; + poke_func(nullptr, nullptr, dst_aligned, value); + ASSERT_EQ(0, memcmp(reinterpret_cast<void*>(dst_aligned), &value, sizeof(T))); + } + free(dst); +} + +TEST(libcore, Memory_pokeShortNative_align_check) { + memory_poke_test<jshort>(Memory_pokeShortNative, 0x0102); +} + +TEST(libcore, Memory_pokeIntNative_align_check) { + memory_poke_test<jint>(Memory_pokeIntNative, 0x01020304); +} + +TEST(libcore, Memory_pokeLongNative_align_check) { + memory_poke_test<jlong>(Memory_pokeLongNative, 0x0102030405060708ULL); +} diff --git a/luni/src/test/native/test_openssl_engine.cpp b/luni/src/test/native/test_openssl_engine.cpp deleted file mode 100644 index 9a0f3b3..0000000 --- a/luni/src/test/native/test_openssl_engine.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2013 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. - */ - -#include "UniquePtr.h" - -#include <stdarg.h> -#include <string.h> -#include <unistd.h> - -#include <openssl/objects.h> -#include <openssl/engine.h> -#include <openssl/evp.h> -#include <openssl/pem.h> - -#define DYNAMIC_ENGINE -#define TEST_ENGINE_ID "javacoretests" -#define TEST_ENGINE_NAME "libcore test engine" - -struct RSA_Delete { - void operator()(RSA* p) const { - RSA_free(p); - } -}; -typedef UniquePtr<RSA, RSA_Delete> Unique_RSA; - -static const char* HMAC_TAG = "-HMAC-"; -static const size_t HMAC_TAG_LEN = strlen(HMAC_TAG); - -static EVP_PKEY *test_load_key(ENGINE* e, const char *key_id, - EVP_PKEY* (*read_func)(BIO*, EVP_PKEY**, pem_password_cb*, void*)) { - void* data = static_cast<void*>(const_cast<char*>(key_id)); - - EVP_PKEY *key = NULL; - - const size_t key_len = strlen(key_id); - if (key_len > HMAC_TAG_LEN && !strncmp(key_id, HMAC_TAG, HMAC_TAG_LEN)) { - key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, e, reinterpret_cast<const unsigned char*>(key_id), - key_len); - } else { - BIO* in = BIO_new_mem_buf(data, strlen(key_id)); - if (!in) { - return NULL; - } - key = read_func(in, NULL, 0, NULL); - BIO_free(in); - - if (key != NULL && EVP_PKEY_type(key->type) == EVP_PKEY_RSA) { - ENGINE_init(e); - - Unique_RSA rsa(EVP_PKEY_get1_RSA(key)); - rsa->engine = e; - rsa->flags |= RSA_FLAG_EXT_PKEY; - } - } - - return key; -} - -static EVP_PKEY* test_load_privkey(ENGINE* e, const char* key_id, UI_METHOD*, void*) { - return test_load_key(e, key_id, PEM_read_bio_PrivateKey); -} - -static EVP_PKEY* test_load_pubkey(ENGINE* e, const char* key_id, UI_METHOD*, void*) { - return test_load_key(e, key_id, PEM_read_bio_PUBKEY); -} - -static const int meths[] = { - EVP_PKEY_HMAC, -}; - -static int pkey_meths(ENGINE*, EVP_PKEY_METHOD** meth, const int** nids, int nid) { - if (nid == EVP_PKEY_HMAC) { - *meth = const_cast<EVP_PKEY_METHOD*>(EVP_PKEY_meth_find(nid)); - return 1; - } else if (nid != 0) { - return 0; - } - - if (nids != NULL) { - *nids = meths; - return 1; - } - - return 0; -} - -static int test_engine_setup(ENGINE* e) { - if (!ENGINE_set_id(e, TEST_ENGINE_ID) - || !ENGINE_set_name(e, TEST_ENGINE_NAME) - || !ENGINE_set_flags(e, 0) - || !ENGINE_set_RSA(e, RSA_get_default_method()) - || !ENGINE_set_load_privkey_function(e, test_load_privkey) - || !ENGINE_set_load_pubkey_function(e, test_load_pubkey) - || !ENGINE_set_pkey_meths(e, pkey_meths)) { - return 0; - } - - return 1; -} - -static int test_engine_bind_fn(ENGINE *e, const char *id) { - if (id && (strcmp(id, TEST_ENGINE_ID) != 0)) { - return 0; - } - - if (!test_engine_setup(e)) { - return 0; - } - - return 1; -} - -extern "C" { -#undef OPENSSL_EXPORT -#define OPENSSL_EXPORT extern __attribute__ ((visibility ("default"))) - -IMPLEMENT_DYNAMIC_CHECK_FN() -IMPLEMENT_DYNAMIC_BIND_FN(test_engine_bind_fn) -}; diff --git a/luni/src/test/resources/META-INF/services/java.nio.charset.spi.CharsetProvider b/luni/src/test/resources/META-INF/services/java.nio.charset.spi.CharsetProvider new file mode 100644 index 0000000..1a53312 --- /dev/null +++ b/luni/src/test/resources/META-INF/services/java.nio.charset.spi.CharsetProvider @@ -0,0 +1 @@ +libcore.java.nio.charset.SettableCharsetProvider |