diff options
Diffstat (limited to 'luni/src/main/java')
311 files changed, 15995 insertions, 11652 deletions
diff --git a/luni/src/main/java/android/system/ErrnoException.java b/luni/src/main/java/android/system/ErrnoException.java new file mode 100644 index 0000000..90155c8 --- /dev/null +++ b/luni/src/main/java/android/system/ErrnoException.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 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 java.io.IOException; +import java.net.SocketException; +import libcore.io.Libcore; + +/** + * A checked exception thrown when {@link Os} methods fail. This exception contains the native + * errno value, for comparison against the constants in {@link OsConstants}, should sophisticated + * callers need to adjust their behavior based on the exact failure. + */ +public final class ErrnoException extends Exception { + private final String functionName; + + /** + * The errno value, for comparison with the {@code E} constants in {@link OsConstants}. + */ + public final int errno; + + /** + * Constructs an instance with the given function name and errno value. + */ + public ErrnoException(String functionName, int errno) { + this.functionName = functionName; + this.errno = errno; + } + + /** + * Constructs an instance with the given function name, errno value, and cause. + */ + public ErrnoException(String functionName, int errno, Throwable cause) { + super(cause); + this.functionName = functionName; + this.errno = errno; + } + + /** + * Converts the stashed function name and errno value to a human-readable string. + * We do this here rather than in the constructor so that callers only pay for + * this if they need it. + */ + @Override public String getMessage() { + String errnoName = OsConstants.errnoName(errno); + if (errnoName == null) { + errnoName = "errno " + errno; + } + String description = Libcore.os.strerror(errno); + return functionName + " failed: " + errnoName + " (" + description + ")"; + } + + /** + * @hide - internal use only. + */ + public IOException rethrowAsIOException() throws IOException { + IOException newException = new IOException(getMessage()); + newException.initCause(this); + throw newException; + } + + /** + * @hide - internal use only. + */ + public SocketException rethrowAsSocketException() throws SocketException { + throw new SocketException(getMessage(), this); + } +} diff --git a/luni/src/main/java/android/system/GaiException.java b/luni/src/main/java/android/system/GaiException.java new file mode 100644 index 0000000..dc10566 --- /dev/null +++ b/luni/src/main/java/android/system/GaiException.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011 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 java.net.UnknownHostException; +import libcore.io.Libcore; + +/** + * An unchecked exception thrown when {@code getaddrinfo} or {@code getnameinfo} fails. + * This exception contains the native {@link #error} value, should sophisticated + * callers need to adjust their behavior based on the exact failure. + * + * @hide + */ +public final class GaiException extends RuntimeException { + private final String functionName; + + /** + * The native error value, for comparison with the {@code GAI_} constants in {@link OsConstants}. + */ + public final int error; + + /** + * Constructs an instance with the given function name and error value. + */ + public GaiException(String functionName, int error) { + this.functionName = functionName; + this.error = error; + } + + /** + * Constructs an instance with the given function name, error value, and cause. + */ + public GaiException(String functionName, int error, Throwable cause) { + super(cause); + this.functionName = functionName; + this.error = error; + } + + /** + * Converts the stashed function name and error value to a human-readable string. + * We do this here rather than in the constructor so that callers only pay for + * this if they need it. + */ + @Override public String getMessage() { + String gaiName = OsConstants.gaiName(error); + if (gaiName == null) { + gaiName = "GAI_ error " + error; + } + String description = Libcore.os.gai_strerror(error); + return functionName + " failed: " + gaiName + " (" + description + ")"; + } + + /** + * @hide - internal use only. + */ + public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException { + UnknownHostException newException = new UnknownHostException(detailMessage); + newException.initCause(this); + throw newException; + } + + /** + * @hide - internal use only. + */ + public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException { + throw rethrowAsUnknownHostException(getMessage()); + } +} diff --git a/luni/src/main/java/android/system/Os.java b/luni/src/main/java/android/system/Os.java new file mode 100644 index 0000000..0b80b52 --- /dev/null +++ b/luni/src/main/java/android/system/Os.java @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2011 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 android.system.ErrnoException; +import android.system.GaiException; +import android.system.StructAddrinfo; +import android.system.StructFlock; +import android.system.StructGroupReq; +import android.system.StructGroupSourceReq; +import android.system.StructLinger; +import android.system.StructPasswd; +import android.system.StructPollfd; +import android.system.StructStat; +import android.system.StructStatVfs; +import android.system.StructTimeval; +import android.system.StructUcred; +import android.system.StructUtsname; +import android.util.MutableInt; +import android.util.MutableLong; +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import libcore.io.Libcore; + +/** + * Access to low-level system functionality. Most of these are system calls. Most users will want + * to use higher-level APIs where available, but this class provides access to the underlying + * primitives used to implement the higher-level APIs. + * + * <p>The corresponding constants can be found in {@link OsConstants}. + */ +public final class Os { + private Os() {} + + /** + * See <a href="http://man7.org/linux/man-pages/man2/accept.2.html">accept(2)</a>. + */ + public static FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException { return Libcore.os.accept(fd, peerAddress); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/access.2.html">access(2)</a>. + */ + public static boolean access(String path, int mode) throws ErrnoException { return Libcore.os.access(path, mode); } + + /** @hide */ public static InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException { return Libcore.os.android_getaddrinfo(node, hints, netId); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/bind.2.html">bind(2)</a>. + */ + public static void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.bind(fd, address, port); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/chmod.2.html">chmod(2)</a>. + */ + public static void chmod(String path, int mode) throws ErrnoException { Libcore.os.chmod(path, mode); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/chown.2.html">chown(2)</a>. + */ + public static void chown(String path, int uid, int gid) throws ErrnoException { Libcore.os.chown(path, uid, gid); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/close.2.html">close(2)</a>. + */ + public static void close(FileDescriptor fd) throws ErrnoException { Libcore.os.close(fd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/connect.2.html">connect(2)</a>. + */ + public static void connect(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException { Libcore.os.connect(fd, address, port); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/dup.2.html">dup(2)</a>. + */ + public static FileDescriptor dup(FileDescriptor oldFd) throws ErrnoException { return Libcore.os.dup(oldFd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/dup2.2.html">dup2(2)</a>. + */ + public static FileDescriptor dup2(FileDescriptor oldFd, int newFd) throws ErrnoException { return Libcore.os.dup2(oldFd, newFd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/environ.3.html">environ(3)</a>. + */ + public static String[] environ() { return Libcore.os.environ(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/execv.2.html">execv(2)</a>. + */ + public static void execv(String filename, String[] argv) throws ErrnoException { Libcore.os.execv(filename, argv); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/execve.2.html">execve(2)</a>. + */ + public static void execve(String filename, String[] argv, String[] envp) throws ErrnoException { Libcore.os.execve(filename, argv, envp); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/fchmod.2.html">fchmod(2)</a>. + */ + public static void fchmod(FileDescriptor fd, int mode) throws ErrnoException { Libcore.os.fchmod(fd, mode); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/fchown.2.html">fchown(2)</a>. + */ + 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 { return Libcore.os.fcntlFlock(fd, cmd, arg); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/fdatasync.2.html">fdatasync(2)</a>. + */ + public static void fdatasync(FileDescriptor fd) throws ErrnoException { Libcore.os.fdatasync(fd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/fstat.2.html">fstat(2)</a>. + */ + public static StructStat fstat(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstat(fd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/fstatvfs.2.html">fstatvfs(2)</a>. + */ + public static StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { return Libcore.os.fstatvfs(fd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/fsync.2.html">fsync(2)</a>. + */ + public static void fsync(FileDescriptor fd) throws ErrnoException { Libcore.os.fsync(fd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/ftruncate.2.html">ftruncate(2)</a>. + */ + public static void ftruncate(FileDescriptor fd, long length) throws ErrnoException { Libcore.os.ftruncate(fd, length); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/gai_strerror.3.html">gai_strerror(3)</a>. + */ + public static String gai_strerror(int error) { return Libcore.os.gai_strerror(error); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/getegid.2.html">getegid(2)</a>. + */ + public static int getegid() { return Libcore.os.getegid(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/geteuid.2.html">geteuid(2)</a>. + */ + public static int geteuid() { return Libcore.os.geteuid(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/getgid.2.html">getgid(2)</a>. + */ + public static int getgid() { return Libcore.os.getgid(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/getenv.3.html">getenv(3)</a>. + */ + public static String getenv(String name) { return Libcore.os.getenv(name); } + + /** @hide */ public static String getnameinfo(InetAddress address, int flags) throws GaiException { return Libcore.os.getnameinfo(address, flags); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/getpeername.2.html">getpeername(2)</a>. + */ + public static SocketAddress getpeername(FileDescriptor fd) throws ErrnoException { return Libcore.os.getpeername(fd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/getpid.2.html">getpid(2)</a>. + */ + public static int getpid() { return Libcore.os.getpid(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/getppid.2.html">getppid(2)</a>. + */ + public static int getppid() { return Libcore.os.getppid(); } + + /** @hide */ public static StructPasswd getpwnam(String name) throws ErrnoException { return Libcore.os.getpwnam(name); } + + /** @hide */ public static StructPasswd getpwuid(int uid) throws ErrnoException { return Libcore.os.getpwuid(uid); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/getsockname.2.html">getsockname(2)</a>. + */ + public static SocketAddress getsockname(FileDescriptor fd) throws ErrnoException { return Libcore.os.getsockname(fd); } + + /** @hide */ public static int getsockoptByte(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptByte(fd, level, option); } + /** @hide */ public static InetAddress getsockoptInAddr(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInAddr(fd, level, option); } + /** @hide */ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptInt(fd, level, option); } + /** @hide */ public static StructLinger getsockoptLinger(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptLinger(fd, level, option); } + /** @hide */ public static StructTimeval getsockoptTimeval(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptTimeval(fd, level, option); } + /** @hide */ public static StructUcred getsockoptUcred(FileDescriptor fd, int level, int option) throws ErrnoException { return Libcore.os.getsockoptUcred(fd, level, option); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/gettid.2.html">gettid(2)</a>. + */ + public static int gettid() { return Libcore.os.gettid(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/getuid.2.html">getuid(2)</a>. + */ + public static int getuid() { return Libcore.os.getuid(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/if_indextoname.3.html">if_indextoname(3)</a>. + */ + public static String if_indextoname(int index) { return Libcore.os.if_indextoname(index); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/inet_pton.3.html">inet_pton(3)</a>. + */ + public static InetAddress inet_pton(int family, String address) { return Libcore.os.inet_pton(family, address); } + + /** @hide */ public static InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return Libcore.os.ioctlInetAddress(fd, cmd, interfaceName); } + /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return Libcore.os.ioctlInt(fd, cmd, arg); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>. + */ + public static boolean isatty(FileDescriptor fd) { return Libcore.os.isatty(fd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/kill.2.html">kill(2)</a>. + */ + public static void kill(int pid, int signal) throws ErrnoException { Libcore.os.kill(pid, signal); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/lchown.2.html">lchown(2)</a>. + */ + public static void lchown(String path, int uid, int gid) throws ErrnoException { Libcore.os.lchown(path, uid, gid); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/link.2.html">link(2)</a>. + */ + public static void link(String oldPath, String newPath) throws ErrnoException { Libcore.os.link(oldPath, newPath); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/listen.2.html">listen(2)</a>. + */ + public static void listen(FileDescriptor fd, int backlog) throws ErrnoException { Libcore.os.listen(fd, backlog); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/lseek.2.html">lseek(2)</a>. + */ + public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return Libcore.os.lseek(fd, offset, whence); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/lstat.2.html">lstat(2)</a>. + */ + public static StructStat lstat(String path) throws ErrnoException { return Libcore.os.lstat(path); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/mincore.2.html">mincore(2)</a>. + */ + public static void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { Libcore.os.mincore(address, byteCount, vector); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/mkdir.2.html">mkdir(2)</a>. + */ + public static void mkdir(String path, int mode) throws ErrnoException { Libcore.os.mkdir(path, mode); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/mkfifo.3.html">mkfifo(3)</a>. + */ + public static void mkfifo(String path, int mode) throws ErrnoException { Libcore.os.mkfifo(path, mode); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/mlock.2.html">mlock(2)</a>. + */ + public static void mlock(long address, long byteCount) throws ErrnoException { Libcore.os.mlock(address, byteCount); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2)</a>. + */ + public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/msync.2.html">msync(2)</a>. + */ + public static void msync(long address, long byteCount, int flags) throws ErrnoException { Libcore.os.msync(address, byteCount, flags); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/munlock.2.html">munlock(2)</a>. + */ + public static void munlock(long address, long byteCount) throws ErrnoException { Libcore.os.munlock(address, byteCount); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/munmap.2.html">munmap(2)</a>. + */ + public static void munmap(long address, long byteCount) throws ErrnoException { Libcore.os.munmap(address, byteCount); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>. + */ + public static FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return Libcore.os.open(path, flags, mode); } + + /** + * 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(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/poll.2.html">poll(2)</a>. + */ + public static int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return Libcore.os.poll(fds, timeoutMs); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/posix_fallocate.2.html">posix_fallocate(2)</a>. + */ + public static void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { Libcore.os.posix_fallocate(fd, offset, length); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/prctl.2.html">prctl(2)</a>. + */ + public static int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException { return Libcore.os.prctl(option, arg2, arg3, arg4, arg5); }; + + /** + * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>. + */ + public static int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, buffer, offset); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/pread.2.html">pread(2)</a>. + */ + public static int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pread(fd, bytes, byteOffset, byteCount, offset); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>. + */ + public static int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, buffer, offset); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite(2)</a>. + */ + public static int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return Libcore.os.pwrite(fd, bytes, byteOffset, byteCount, offset); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>. + */ + public static int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, buffer); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>. + */ + public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.read(fd, bytes, byteOffset, byteCount); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/readlink.2.html">readlink(2)</a>. + */ + public static String readlink(String path) throws ErrnoException { return Libcore.os.readlink(path); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/readv.2.html">readv(2)</a>. + */ + public static int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.readv(fd, buffers, offsets, byteCounts); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>. + */ + public static int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, buffer, flags, srcAddress); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/recvfrom.2.html">recvfrom(2)</a>. + */ + public static int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/remove.3.html">remove(3)</a>. + */ + public static void remove(String path) throws ErrnoException { Libcore.os.remove(path); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/rename.2.html">rename(2)</a>. + */ + public static void rename(String oldPath, String newPath) throws ErrnoException { Libcore.os.rename(oldPath, newPath); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>. + */ + public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return Libcore.os.sendfile(outFd, inFd, inOffset, byteCount); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>. + */ + public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { return Libcore.os.sendto(fd, buffer, flags, inetAddress, port); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>. + */ + 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/setegid.2.html">setegid(2)</a>. + */ + public static void setegid(int egid) throws ErrnoException { Libcore.os.setegid(egid); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/setenv.3.html">setenv(3)</a>. + */ + public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { Libcore.os.setenv(name, value, overwrite); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/seteuid.2.html">seteuid(2)</a>. + */ + public static void seteuid(int euid) throws ErrnoException { Libcore.os.seteuid(euid); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/setgid.2.html">setgid(2)</a>. + */ + public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); } + + /** + * 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(); } + + /** @hide */ public static void setsockoptByte(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptByte(fd, level, option, value); } + /** @hide */ public static void setsockoptIfreq(FileDescriptor fd, int level, int option, String value) throws ErrnoException { Libcore.os.setsockoptIfreq(fd, level, option, value); } + /** @hide */ public static void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptInt(fd, level, option, value); } + /** @hide */ public static void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptIpMreqn(fd, level, option, value); } + /** @hide */ public static void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { Libcore.os.setsockoptGroupReq(fd, level, option, value); } + /** @hide */ public static void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException { Libcore.os.setsockoptGroupSourceReq(fd, level, option, value); } + /** @hide */ public static void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { Libcore.os.setsockoptLinger(fd, level, option, value); } + /** @hide */ public static void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { Libcore.os.setsockoptTimeval(fd, level, option, value); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/setuid.2.html">setuid(2)</a>. + */ + public static void setuid(int uid) throws ErrnoException { Libcore.os.setuid(uid); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/shutdown.2.html">shutdown(2)</a>. + */ + public static void shutdown(FileDescriptor fd, int how) throws ErrnoException { Libcore.os.shutdown(fd, how); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/socket.2.html">socket(2)</a>. + */ + public static FileDescriptor socket(int domain, int type, int protocol) throws ErrnoException { return Libcore.os.socket(domain, type, protocol); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/socketpair.2.html">socketpair(2)</a>. + */ + public static void socketpair(int domain, int type, int protocol, FileDescriptor fd1, FileDescriptor fd2) throws ErrnoException { Libcore.os.socketpair(domain, type, protocol, fd1, fd2); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/stat.2.html">stat(2)</a>. + */ + public static StructStat stat(String path) throws ErrnoException { return Libcore.os.stat(path); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/statvfs.2.html">statvfs(2)</a>. + */ + public static StructStatVfs statvfs(String path) throws ErrnoException { return Libcore.os.statvfs(path); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/strerror.3.html">strerror(2)</a>. + */ + public static String strerror(int errno) { return Libcore.os.strerror(errno); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/strsignal.3.html">strsignal(3)</a>. + */ + public static String strsignal(int signal) { return Libcore.os.strsignal(signal); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/symlink.2.html">symlink(2)</a>. + */ + public static void symlink(String oldPath, String newPath) throws ErrnoException { Libcore.os.symlink(oldPath, newPath); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/sysconf.3.html">sysconf(3)</a>. + */ + public static long sysconf(int name) { return Libcore.os.sysconf(name); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/tcdrain.3.html">tcdrain(3)</a>. + */ + public static void tcdrain(FileDescriptor fd) throws ErrnoException { Libcore.os.tcdrain(fd); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/tcsendbreak.3.html">tcsendbreak(3)</a>. + */ + public static void tcsendbreak(FileDescriptor fd, int duration) throws ErrnoException { Libcore.os.tcsendbreak(fd, duration); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/umask.2.html">umask(2)</a>. + */ + public static int umask(int mask) { return Libcore.os.umask(mask); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/uname.2.html">uname(2)</a>. + */ + public static StructUtsname uname() { return Libcore.os.uname(); } + + /** + * See <a href="http://man7.org/linux/man-pages/man3/unsetenv.3.html">unsetenv(3)</a>. + */ + public static void unsetenv(String name) throws ErrnoException { Libcore.os.unsetenv(name); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>. + */ + public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return Libcore.os.waitpid(pid, status, options); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>. + */ + public static int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, buffer); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>. + */ + public static int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return Libcore.os.write(fd, bytes, byteOffset, byteCount); } + + /** + * See <a href="http://man7.org/linux/man-pages/man2/writev.2.html">writev(2)</a>. + */ + public static int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return Libcore.os.writev(fd, buffers, offsets, byteCounts); } +} diff --git a/luni/src/main/java/libcore/io/OsConstants.java b/luni/src/main/java/android/system/OsConstants.java index edecdd9..c758eb7 100644 --- a/luni/src/main/java/libcore/io/OsConstants.java +++ b/luni/src/main/java/android/system/OsConstants.java @@ -14,25 +14,83 @@ * limitations under the License. */ -package libcore.io; +package android.system; +/** + * Constants and helper functions for use with {@link Os}. + */ public final class OsConstants { - private OsConstants() { } + private OsConstants() { + } + /** + * Tests whether the given mode is a block device. + */ public static boolean S_ISBLK(int mode) { return (mode & S_IFMT) == S_IFBLK; } + + /** + * Tests whether the given mode is a character device. + */ public static boolean S_ISCHR(int mode) { return (mode & S_IFMT) == S_IFCHR; } + + /** + * Tests whether the given mode is a directory. + */ public static boolean S_ISDIR(int mode) { return (mode & S_IFMT) == S_IFDIR; } + + /** + * Tests whether the given mode is a FIFO. + */ public static boolean S_ISFIFO(int mode) { return (mode & S_IFMT) == S_IFIFO; } + + /** + * Tests whether the given mode is a regular file. + */ public static boolean S_ISREG(int mode) { return (mode & S_IFMT) == S_IFREG; } + + /** + * Tests whether the given mode is a symbolic link. + */ public static boolean S_ISLNK(int mode) { return (mode & S_IFMT) == S_IFLNK; } + + /** + * Tests whether the given mode is a socket. + */ public static boolean S_ISSOCK(int mode) { return (mode & S_IFMT) == S_IFSOCK; } + /** + * Extracts the exit status of a child. Only valid if WIFEXITED returns true. + */ public static int WEXITSTATUS(int status) { return (status & 0xff00) >> 8; } + + /** + * Tests whether the child dumped core. Only valid if WIFSIGNALED returns true. + */ public static boolean WCOREDUMP(int status) { return (status & 0x80) != 0; } + + /** + * Returns the signal that caused the child to exit. Only valid if WIFSIGNALED returns true. + */ public static int WTERMSIG(int status) { return status & 0x7f; } + + /** + * Returns the signal that cause the child to stop. Only valid if WIFSTOPPED returns true. + */ public static int WSTOPSIG(int status) { return WEXITSTATUS(status); } + + /** + * Tests whether the child exited normally. + */ public static boolean WIFEXITED(int status) { return (WTERMSIG(status) == 0); } + + /** + * Tests whether the child was stopped (not terminated) by a signal. + */ public static boolean WIFSTOPPED(int status) { return (WTERMSIG(status) == 0x7f); } + + /** + * Tests whether the child was terminated by a signal. + */ public static boolean WIFSIGNALED(int status) { return (WTERMSIG(status + 1) >= 2); } public static final int AF_INET = placeholder(); @@ -48,6 +106,7 @@ public final class OsConstants { public static final int AI_V4MAPPED = 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(); public static final int CAP_CHOWN = placeholder(); public static final int CAP_DAC_OVERRIDE = placeholder(); public static final int CAP_DAC_READ_SEARCH = placeholder(); @@ -194,6 +253,15 @@ public final class OsConstants { public static final int F_SETOWN = placeholder(); public static final int F_UNLCK = placeholder(); public static final int F_WRLCK = placeholder(); + public static final int IFA_F_DADFAILED = placeholder(); + public static final int IFA_F_DEPRECATED = placeholder(); + public static final int IFA_F_HOMEADDRESS = placeholder(); + public static final int IFA_F_NODAD = placeholder(); + public static final int IFA_F_OPTIMISTIC = placeholder(); + public static final int IFA_F_PERMANENT = placeholder(); + public static final int IFA_F_SECONDARY = placeholder(); + public static final int IFA_F_TEMPORARY = placeholder(); + public static final int IFA_F_TENTATIVE = placeholder(); public static final int IFF_ALLMULTI = placeholder(); public static final int IFF_AUTOMEDIA = placeholder(); public static final int IFF_BROADCAST = placeholder(); @@ -240,6 +308,10 @@ public final class OsConstants { public static final int MAP_SHARED = placeholder(); public static final int MCAST_JOIN_GROUP = placeholder(); public static final int MCAST_LEAVE_GROUP = placeholder(); + public static final int MCAST_JOIN_SOURCE_GROUP = placeholder(); + public static final int MCAST_LEAVE_SOURCE_GROUP = placeholder(); + public static final int MCAST_BLOCK_SOURCE = placeholder(); + public static final int MCAST_UNBLOCK_SOURCE = placeholder(); public static final int MCL_CURRENT = placeholder(); public static final int MCL_FUTURE = placeholder(); public static final int MSG_CTRUNC = placeholder(); @@ -279,11 +351,19 @@ public final class OsConstants { public static final int POLLRDNORM = placeholder(); public static final int POLLWRBAND = placeholder(); public static final int POLLWRNORM = placeholder(); + public static final int PR_GET_DUMPABLE = placeholder(); + public static final int PR_SET_DUMPABLE = placeholder(); + public static final int PR_SET_NO_NEW_PRIVS = placeholder(); public static final int PROT_EXEC = placeholder(); public static final int PROT_NONE = placeholder(); public static final int PROT_READ = placeholder(); public static final int PROT_WRITE = placeholder(); public static final int R_OK = placeholder(); + public static final int RT_SCOPE_HOST = placeholder(); + public static final int RT_SCOPE_LINK = placeholder(); + public static final int RT_SCOPE_NOWHERE = placeholder(); + public static final int RT_SCOPE_SITE = placeholder(); + public static final int RT_SCOPE_UNIVERSE = placeholder(); public static final int SEEK_CUR = placeholder(); public static final int SEEK_END = placeholder(); public static final int SEEK_SET = placeholder(); @@ -476,6 +556,10 @@ public final class OsConstants { public static final int _SC_XOPEN_VERSION = placeholder(); public static final int _SC_XOPEN_XCU_VERSION = placeholder(); + /** + * Returns the string name of a getaddrinfo(3) error value. + * For example, "EAI_AGAIN". + */ public static String gaiName(int error) { if (error == EAI_AGAIN) { return "EAI_AGAIN"; @@ -513,6 +597,10 @@ public final class OsConstants { return null; } + /** + * Returns the string name of an errno value. + * For example, "EACCES". See {@link Os#strerror} for human-readable errno descriptions. + */ public static String errnoName(int errno) { if (errno == E2BIG) { return "E2BIG"; diff --git a/luni/src/main/java/libcore/io/StructAddrinfo.java b/luni/src/main/java/android/system/StructAddrinfo.java index 8c8181d..2425946 100644 --- a/luni/src/main/java/libcore/io/StructAddrinfo.java +++ b/luni/src/main/java/android/system/StructAddrinfo.java @@ -14,38 +14,45 @@ * limitations under the License. */ -package libcore.io; +package android.system; import java.net.InetAddress; +import libcore.util.Objects; /** * Information returned/taken by getaddrinfo(3). Corresponds to C's {@code struct addrinfo} from * <a href="http://pubs.opengroup.org/onlinepubs/009695399/basedefs/netdb.h.html"><netdb.h></a> * * TODO: we currently only _take_ a StructAddrinfo; getaddrinfo returns an InetAddress[]. + * + * @hide */ public final class StructAddrinfo { - /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */ - public int ai_flags; + /** Flags describing the kind of lookup to be done. (Such as AI_ADDRCONFIG.) */ + public int ai_flags; + + /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */ + public int ai_family; - /** Desired address family for results. (Such as AF_INET6 for IPv6. AF_UNSPEC means "any".) */ - public int ai_family; + /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */ + public int ai_socktype; - /** Socket type. (Such as SOCK_DGRAM. 0 means "any".) */ - public int ai_socktype; + /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */ + public int ai_protocol; - /** Protocol. (Such as IPPROTO_IPV6 IPv6. 0 means "any".) */ - public int ai_protocol; + /** Address length. (Not useful in Java.) */ + // public int ai_addrlen; - /** Address length. (Not useful in Java.) */ - // public int ai_addrlen; + /** Address. */ + public InetAddress ai_addr; - /** Address. */ - public InetAddress ai_addr; + /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */ + // public String ai_canonname; - /** Canonical name of service location (if AI_CANONNAME provided in ai_flags). */ - // public String ai_canonname; + /** Next element in linked list. */ + public StructAddrinfo ai_next; - /** Next element in linked list. */ - public StructAddrinfo ai_next; + @Override public String toString() { + return Objects.toString(this); + } } diff --git a/luni/src/main/java/libcore/io/StructFlock.java b/luni/src/main/java/android/system/StructFlock.java index 11c29df..92cd95a 100644 --- a/luni/src/main/java/libcore/io/StructFlock.java +++ b/luni/src/main/java/android/system/StructFlock.java @@ -14,26 +14,34 @@ * limitations under the License. */ -package libcore.io; +package android.system; + +import libcore.util.Objects; /** * Information returned/taken by fcntl(2) F_GETFL and F_SETFL. Corresponds to C's * {@code struct flock} from * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/fcntl.h.html"><fcntl.h></a> + * + * @hide */ public final class StructFlock { - /** The operation type, one of F_RDLCK, F_WRLCK, or F_UNLCK. */ - public short l_type; + /** The operation type, one of F_RDLCK, F_WRLCK, or F_UNLCK. */ + public short l_type; + + /** How to interpret l_start, one of SEEK_CUR, SEEK_END, SEEK_SET. */ + public short l_whence; - /** How to interpret l_start, one of SEEK_CUR, SEEK_END, SEEK_SET. */ - public short l_whence; + /** Start offset. */ + public long l_start; /*off_t*/ - /** Start offset. */ - public long l_start; /*off_t*/ + /** Byte count to operate on. */ + public long l_len; /*off_t*/ - /** Byte count to operate on. */ - public long l_len; /*off_t*/ + /** Process blocking our lock (filled in by F_GETLK, otherwise unused). */ + public int l_pid; /*pid_t*/ - /** Process blocking our lock (filled in by F_GETLK, otherwise unused). */ - public int l_pid; /*pid_t*/ + @Override public String toString() { + return Objects.toString(this); + } } diff --git a/luni/src/main/java/libcore/io/StructGroupReq.java b/luni/src/main/java/android/system/StructGroupReq.java index 0bdf783..8ed5950 100644 --- a/luni/src/main/java/libcore/io/StructGroupReq.java +++ b/luni/src/main/java/android/system/StructGroupReq.java @@ -14,23 +14,26 @@ * limitations under the License. */ -package libcore.io; +package android.system; import java.net.InetAddress; +import libcore.util.Objects; /** * Corresponds to C's {@code struct group_req}. + * + * @hide */ public final class StructGroupReq { - public final int gr_interface; - public final InetAddress gr_group; + public final int gr_interface; + public final InetAddress gr_group; - public StructGroupReq(int gr_interface, InetAddress gr_group) { - this.gr_interface = gr_interface; - this.gr_group = gr_group; - } + public StructGroupReq(int gr_interface, InetAddress gr_group) { + this.gr_interface = gr_interface; + this.gr_group = gr_group; + } - @Override public String toString() { - return "StructGroupReq[gr_interface=" + gr_interface + ",gr_group=" + gr_group + "]"; - } + @Override public String toString() { + return Objects.toString(this); + } } diff --git a/luni/src/main/java/android/system/StructGroupSourceReq.java b/luni/src/main/java/android/system/StructGroupSourceReq.java new file mode 100644 index 0000000..c300338 --- /dev/null +++ b/luni/src/main/java/android/system/StructGroupSourceReq.java @@ -0,0 +1,41 @@ +/* + * 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 android.system; + +import java.net.InetAddress; +import libcore.util.Objects; + +/** + * Corresponds to C's {@code struct group_source_req}. + * + * @hide + */ +public final class StructGroupSourceReq { + public final int gsr_interface; + public final InetAddress gsr_group; + public final InetAddress gsr_source; + + public StructGroupSourceReq(int gsr_interface, InetAddress gsr_group, InetAddress gsr_source) { + this.gsr_interface = gsr_interface; + this.gsr_group = gsr_group; + this.gsr_source = gsr_source; + } + + @Override public String toString() { + return Objects.toString(this); + } +} diff --git a/luni/src/main/java/libcore/io/StructLinger.java b/luni/src/main/java/android/system/StructLinger.java index 9f149af..55ffc5c 100644 --- a/luni/src/main/java/libcore/io/StructLinger.java +++ b/luni/src/main/java/android/system/StructLinger.java @@ -14,29 +14,33 @@ * limitations under the License. */ -package libcore.io; +package android.system; + +import libcore.util.Objects; /** * Corresponds to C's {@code struct linger} from * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_socket.h.html"><sys/socket.h></a> + * + * @hide */ public final class StructLinger { - /** Whether or not linger is enabled. Non-zero is on. */ - public final int l_onoff; + /** Whether or not linger is enabled. Non-zero is on. */ + public final int l_onoff; - /** Linger time in seconds. */ - public final int l_linger; + /** Linger time in seconds. */ + public final int l_linger; - public StructLinger(int l_onoff, int l_linger) { - this.l_onoff = l_onoff; - this.l_linger = l_linger; - } + public StructLinger(int l_onoff, int l_linger) { + this.l_onoff = l_onoff; + this.l_linger = l_linger; + } - public boolean isOn() { - return l_onoff != 0; - } + public boolean isOn() { + return l_onoff != 0; + } - @Override public String toString() { - return "StructLinger[l_onoff=" + l_onoff + ",l_linger=" + l_linger + "]"; - } + @Override public String toString() { + return Objects.toString(this); + } } diff --git a/luni/src/main/java/android/system/StructPasswd.java b/luni/src/main/java/android/system/StructPasswd.java new file mode 100644 index 0000000..04a7826 --- /dev/null +++ b/luni/src/main/java/android/system/StructPasswd.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011 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; + +/** + * Information returned by {@link Os#getpwnam} and {@link Os#getpwuid}. Corresponds to C's + * {@code struct passwd} from {@code <pwd.h>}. + * + * @hide + */ +public final class StructPasswd { + public final String pw_name; + public final int pw_uid; + public final int pw_gid; + public final String pw_dir; + public final String pw_shell; + + /** + * Constructs an instance with the given field values. + */ + public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) { + this.pw_name = pw_name; + this.pw_uid = pw_uid; + this.pw_gid = pw_gid; + this.pw_dir = pw_dir; + this.pw_shell = pw_shell; + } + + @Override public String toString() { + return Objects.toString(this); + } +} diff --git a/luni/src/main/java/android/system/StructPollfd.java b/luni/src/main/java/android/system/StructPollfd.java new file mode 100644 index 0000000..b812612 --- /dev/null +++ b/luni/src/main/java/android/system/StructPollfd.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 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 java.io.FileDescriptor; +import libcore.util.Objects; + +/** + * Used as an in/out parameter to {@link Os#poll}. + * Corresponds to C's {@code struct pollfd} from {@code <poll.h>}. + */ +public final class StructPollfd { + /** The file descriptor to poll. */ + public FileDescriptor fd; + + /** + * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set, + * POLLOUT to the write fd set. + */ + public short events; + + /** The events that actually happened. */ + public short revents; + + /** + * A non-standard extension that lets callers conveniently map back to the object + * their fd belongs to. This is used by Selector, for example, to associate each + * FileDescriptor with the corresponding SelectionKey. + */ + public Object userData; + + @Override public String toString() { + return Objects.toString(this); + } +} diff --git a/luni/src/main/java/android/system/StructStat.java b/luni/src/main/java/android/system/StructStat.java new file mode 100644 index 0000000..a6958c1 --- /dev/null +++ b/luni/src/main/java/android/system/StructStat.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 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; + +/** + * File information returned by {@link Os#fstat}, {@link Os#lstat}, and {@link Os#stat}. + * Corresponds to C's {@code struct stat} from {@code <stat.h>}. + */ +public final class StructStat { + /** Device ID of device containing file. */ + public final long st_dev; /*dev_t*/ + + /** File serial number (inode). */ + public final long st_ino; /*ino_t*/ + + /** Mode (permissions) of file. */ + public final int st_mode; /*mode_t*/ + + /** Number of hard links to the file. */ + public final long st_nlink; /*nlink_t*/ + + /** User ID of file. */ + public final int st_uid; /*uid_t*/ + + /** Group ID of file. */ + public final int st_gid; /*gid_t*/ + + /** Device ID (if file is character or block special). */ + public final long st_rdev; /*dev_t*/ + + /** + * For regular files, the file size in bytes. + * For symbolic links, the length in bytes of the pathname contained in the symbolic link. + * For a shared memory object, the length in bytes. + * For a typed memory object, the length in bytes. + * For other file types, the use of this field is unspecified. + */ + public final long st_size; /*off_t*/ + + /** Time of last access. */ + public final long st_atime; /*time_t*/ + + /** Time of last data modification. */ + public final long st_mtime; /*time_t*/ + + /** Time of last status change. */ + public final long st_ctime; /*time_t*/ + + /** + * A file system-specific preferred I/O block size for this object. + * For some file system types, this may vary from file to file. + */ + public final long st_blksize; /*blksize_t*/ + + /** Number of blocks allocated for this object. */ + public final long st_blocks; /*blkcnt_t*/ + + /** + * Constructs an instance with the given field values. + */ + public StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid, + long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime, + long st_blksize, long st_blocks) { + this.st_dev = st_dev; + this.st_ino = st_ino; + this.st_mode = st_mode; + this.st_nlink = st_nlink; + this.st_uid = st_uid; + this.st_gid = st_gid; + this.st_rdev = st_rdev; + this.st_size = st_size; + this.st_atime = st_atime; + this.st_mtime = st_mtime; + this.st_ctime = st_ctime; + this.st_blksize = st_blksize; + this.st_blocks = st_blocks; + } + + @Override public String toString() { + return Objects.toString(this); + } +} diff --git a/luni/src/main/java/libcore/io/StructStatVfs.java b/luni/src/main/java/android/system/StructStatVfs.java index bb78ff2..942a39a 100644 --- a/luni/src/main/java/libcore/io/StructStatVfs.java +++ b/luni/src/main/java/android/system/StructStatVfs.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package libcore.io; +package android.system; + +import libcore.util.Objects; /** - * File information returned by fstatvfs(2) and statvfs(2). + * File information returned by {@link Os#fstatvfs} and {@link Os#statvfs}. */ public final class StructStatVfs { /** File system block size (used for block counts). */ @@ -53,7 +55,10 @@ public final class StructStatVfs { /** Maximum filename length. */ public final long f_namemax; /*unsigned long*/ - StructStatVfs(long f_bsize, long f_frsize, long f_blocks, long f_bfree, long f_bavail, + /** + * Constructs an instance with the given field values. + */ + public StructStatVfs(long f_bsize, long f_frsize, long f_blocks, long f_bfree, long f_bavail, long f_files, long f_ffree, long f_favail, long f_fsid, long f_flag, long f_namemax) { this.f_bsize = f_bsize; @@ -68,4 +73,8 @@ public final class StructStatVfs { this.f_flag = f_flag; this.f_namemax = f_namemax; } + + @Override public String toString() { + return Objects.toString(this); + } } diff --git a/luni/src/main/java/libcore/io/StructTimeval.java b/luni/src/main/java/android/system/StructTimeval.java index 0ed3509..8a155b4 100644 --- a/luni/src/main/java/libcore/io/StructTimeval.java +++ b/luni/src/main/java/android/system/StructTimeval.java @@ -14,35 +14,39 @@ * limitations under the License. */ -package libcore.io; +package android.system; + +import libcore.util.Objects; /** * Corresponds to C's {@code struct timeval} from * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_time.h.html"><sys/time.h></a> + * + * @hide */ public final class StructTimeval { - /** Seconds. */ - public final long tv_sec; - - /** Microseconds. */ - public final long tv_usec; - - private StructTimeval(long tv_sec, long tv_usec) { - this.tv_sec = tv_sec; - this.tv_usec = tv_usec; - } - - public static StructTimeval fromMillis(long millis) { - long tv_sec = millis / 1000; - long tv_usec = (millis - (tv_sec * 1000)) * 1000; - return new StructTimeval(tv_sec, tv_usec); - } - - public long toMillis() { - return (tv_sec * 1000) + (tv_usec / 1000); - } - - @Override public String toString() { - return "StructTimeval[tv_sec=" + tv_sec + ",tv_usec=" + tv_usec + "]"; - } + /** Seconds. */ + public final long tv_sec; + + /** Microseconds. */ + public final long tv_usec; + + private StructTimeval(long tv_sec, long tv_usec) { + this.tv_sec = tv_sec; + this.tv_usec = tv_usec; + } + + public static StructTimeval fromMillis(long millis) { + long tv_sec = millis / 1000; + long tv_usec = (millis - (tv_sec * 1000)) * 1000; + return new StructTimeval(tv_sec, tv_usec); + } + + public long toMillis() { + return (tv_sec * 1000) + (tv_usec / 1000); + } + + @Override public String toString() { + return Objects.toString(this); + } } diff --git a/luni/src/main/java/libcore/io/StructUcred.java b/luni/src/main/java/android/system/StructUcred.java index 359995d..a1e3cd6 100644 --- a/luni/src/main/java/libcore/io/StructUcred.java +++ b/luni/src/main/java/android/system/StructUcred.java @@ -14,10 +14,14 @@ * limitations under the License. */ -package libcore.io; +package android.system; + +import libcore.util.Objects; /** * Corresponds to C's {@code struct ucred}. + * + * @hide */ public final class StructUcred { /** The peer's process id. */ @@ -29,13 +33,13 @@ public final class StructUcred { /** The peer process' gid. */ public final int gid; - private StructUcred(int pid, int uid, int gid) { + public StructUcred(int pid, int uid, int gid) { this.pid = pid; this.uid = uid; this.gid = gid; } @Override public String toString() { - return "StructUcred[pid=" + pid + ",uid=" + uid + ",gid=" + gid + "]"; + return Objects.toString(this); } } diff --git a/luni/src/main/java/android/system/StructUtsname.java b/luni/src/main/java/android/system/StructUtsname.java new file mode 100644 index 0000000..5d9127b --- /dev/null +++ b/luni/src/main/java/android/system/StructUtsname.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 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; + +/** + * Information returned by {@link Os#uname}. + * Corresponds to C's {@code struct utsname} from {@code <sys/utsname.h>}. + */ +public final class StructUtsname { + /** The OS name, such as "Linux". */ + public final String sysname; + + /** The machine's unqualified name on some implementation-defined network. */ + public final String nodename; + + /** The OS release, such as "2.6.35-27-generic". */ + public final String release; + + /** The OS version, such as "#48-Ubuntu SMP Tue Feb 22 20:25:29 UTC 2011". */ + public final String version; + + /** The machine architecture, such as "armv7l" or "x86_64". */ + public final String machine; + + /** + * Constructs an instance with the given field values. + */ + public StructUtsname(String sysname, String nodename, String release, String version, String machine) { + this.sysname = sysname; + this.nodename = nodename; + this.release = release; + this.version = version; + this.machine = machine; + } + + @Override public String toString() { + return Objects.toString(this); + } +} diff --git a/luni/src/main/java/libcore/util/MutableBoolean.java b/luni/src/main/java/android/util/MutableBoolean.java index 359a8f9..5a8a200 100644 --- a/luni/src/main/java/libcore/util/MutableBoolean.java +++ b/luni/src/main/java/android/util/MutableBoolean.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package libcore.util; +package android.util; +/** + */ public final class MutableBoolean { - public boolean value; + public boolean value; - public MutableBoolean(boolean value) { - this.value = value; - } + public MutableBoolean(boolean value) { + this.value = value; + } } diff --git a/luni/src/main/java/libcore/util/MutableByte.java b/luni/src/main/java/android/util/MutableByte.java index 13f780b..7397ba4 100644 --- a/luni/src/main/java/libcore/util/MutableByte.java +++ b/luni/src/main/java/android/util/MutableByte.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package libcore.util; +package android.util; +/** + */ public final class MutableByte { - public byte value; + public byte value; - public MutableByte(byte value) { - this.value = value; - } + public MutableByte(byte value) { + this.value = value; + } } diff --git a/luni/src/main/java/libcore/util/MutableChar.java b/luni/src/main/java/android/util/MutableChar.java index 1cafc3c..f435331 100644 --- a/luni/src/main/java/libcore/util/MutableChar.java +++ b/luni/src/main/java/android/util/MutableChar.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package libcore.util; +package android.util; +/** + */ public final class MutableChar { - public char value; + public char value; - public MutableChar(char value) { - this.value = value; - } + public MutableChar(char value) { + this.value = value; + } } diff --git a/luni/src/main/java/libcore/util/MutableDouble.java b/luni/src/main/java/android/util/MutableDouble.java index 4473ae6..f62f47e 100644 --- a/luni/src/main/java/libcore/util/MutableDouble.java +++ b/luni/src/main/java/android/util/MutableDouble.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package libcore.util; +package android.util; +/** + */ public final class MutableDouble { - public double value; + public double value; - public MutableDouble(double value) { - this.value = value; - } + public MutableDouble(double value) { + this.value = value; + } } diff --git a/luni/src/main/java/libcore/util/MutableFloat.java b/luni/src/main/java/android/util/MutableFloat.java index f81fba5..6b5441c 100644 --- a/luni/src/main/java/libcore/util/MutableFloat.java +++ b/luni/src/main/java/android/util/MutableFloat.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package libcore.util; +package android.util; +/** + */ public final class MutableFloat { - public float value; + public float value; - public MutableFloat(float value) { - this.value = value; - } + public MutableFloat(float value) { + this.value = value; + } } diff --git a/luni/src/main/java/libcore/util/MutableInt.java b/luni/src/main/java/android/util/MutableInt.java index c8feb3a..2f93030 100644 --- a/luni/src/main/java/libcore/util/MutableInt.java +++ b/luni/src/main/java/android/util/MutableInt.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package libcore.util; +package android.util; +/** + */ public final class MutableInt { - public int value; + public int value; - public MutableInt(int value) { - this.value = value; - } + public MutableInt(int value) { + this.value = value; + } } diff --git a/luni/src/main/java/libcore/util/MutableLong.java b/luni/src/main/java/android/util/MutableLong.java index ad9b78e..94beab5 100644 --- a/luni/src/main/java/libcore/util/MutableLong.java +++ b/luni/src/main/java/android/util/MutableLong.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package libcore.util; +package android.util; +/** + */ public final class MutableLong { - public long value; + public long value; - public MutableLong(long value) { - this.value = value; - } + public MutableLong(long value) { + this.value = value; + } } diff --git a/luni/src/main/java/libcore/util/MutableShort.java b/luni/src/main/java/android/util/MutableShort.java index 78b4c33..cdd9923 100644 --- a/luni/src/main/java/libcore/util/MutableShort.java +++ b/luni/src/main/java/android/util/MutableShort.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package libcore.util; +package android.util; +/** + */ public final class MutableShort { - public short value; + public short value; - public MutableShort(short value) { - this.value = value; - } + public MutableShort(short value) { + this.value = value; + } } diff --git a/luni/src/main/java/java/io/BufferedInputStream.java b/luni/src/main/java/java/io/BufferedInputStream.java index 5a810e7..85236b6 100644 --- a/luni/src/main/java/java/io/BufferedInputStream.java +++ b/luni/src/main/java/java/io/BufferedInputStream.java @@ -37,6 +37,13 @@ import java.util.Arrays; */ public class BufferedInputStream extends FilterInputStream { /** + * The default buffer size if it is not specified. + * + * @hide + */ + public static final int DEFAULT_BUFFER_SIZE = 8192; + + /** * The buffer containing the current bytes read from the target InputStream. */ protected volatile byte[] buf; @@ -73,7 +80,7 @@ public class BufferedInputStream extends FilterInputStream { * @param in the {@code InputStream} the buffer reads from. */ public BufferedInputStream(InputStream in) { - this(in, 8192); + this(in, DEFAULT_BUFFER_SIZE); } /** @@ -325,7 +332,7 @@ public class BufferedInputStream extends FilterInputStream { if (buf == null) { throw new IOException("Stream is closed"); } - if (-1 == markpos) { + if (markpos == -1) { throw new IOException("Mark has been invalidated."); } pos = markpos; diff --git a/luni/src/main/java/java/io/Console.java b/luni/src/main/java/java/io/Console.java index a1d2097..fe07694c 100644 --- a/luni/src/main/java/java/io/Console.java +++ b/luni/src/main/java/java/io/Console.java @@ -16,9 +16,7 @@ package java.io; import java.util.Formatter; -import libcore.io.ErrnoException; import libcore.io.Libcore; -import static libcore.io.OsConstants.*; /** * Provides access to the console, if available. The system-wide instance can @@ -48,12 +46,12 @@ public final class Console implements Flushable { } try { return new Console(System.in, System.out); - } catch (IOException ex) { + } catch (UnsupportedEncodingException ex) { throw new AssertionError(ex); } } - private Console(InputStream in, OutputStream out) throws IOException { + private Console(InputStream in, OutputStream out) throws UnsupportedEncodingException { this.reader = new ConsoleReader(in); this.writer = new ConsoleWriter(out); } @@ -129,48 +127,17 @@ public final class Console implements Flushable { } /** - * Reads a password from the console. The password will not be echoed to the display. - * - * @return a character array containing the password, or null at EOF. + * This method is unimplemented on Android. */ public char[] readPassword() { - synchronized (CONSOLE_LOCK) { - int previousState = setEcho(false, 0); - try { - String password = readLine(); - writer.println(); // We won't have echoed the user's newline. - return (password == null) ? null : password.toCharArray(); - } finally { - setEcho(true, previousState); - } - } - } - - private static int setEcho(boolean on, int previousState) { - try { - return setEchoImpl(on, previousState); - } catch (IOException ex) { - throw new IOError(ex); - } + throw new UnsupportedOperationException(); } - private static native int setEchoImpl(boolean on, int previousState) throws IOException; /** - * Reads a password from the console. The password will not be echoed to the display. - * A formatted prompt is also displayed. - * - * @param format the format string (see {@link java.util.Formatter#format}) - * @param args - * the list of arguments passed to the formatter. If there are - * more arguments than required by {@code format}, - * additional arguments are ignored. - * @return a character array containing the password, or null at EOF. + * This method is unimplemented on Android. */ public char[] readPassword(String format, Object... args) { - synchronized (CONSOLE_LOCK) { - format(format, args); - return readPassword(); - } + throw new UnsupportedOperationException(); } /** @@ -181,7 +148,7 @@ public final class Console implements Flushable { } private static class ConsoleReader extends BufferedReader { - public ConsoleReader(InputStream in) throws IOException { + public ConsoleReader(InputStream in) throws UnsupportedEncodingException { super(new InputStreamReader(in, System.getProperty("file.encoding")), 256); lock = CONSOLE_LOCK; } diff --git a/luni/src/main/java/java/io/File.java b/luni/src/main/java/java/io/File.java index a8b4810..d107c28 100644 --- a/luni/src/main/java/java/io/File.java +++ b/luni/src/main/java/java/io/File.java @@ -17,19 +17,19 @@ package java.io; +import android.system.ErrnoException; +import android.system.StructStat; +import android.system.StructStatVfs; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Random; -import libcore.io.ErrnoException; +import libcore.io.DeleteOnExit; import libcore.io.IoUtils; import libcore.io.Libcore; -import libcore.io.StructStat; -import libcore.io.StructStatVfs; -import org.apache.harmony.luni.util.DeleteOnExit; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * An "abstract" representation of a file system entity identified by a @@ -411,15 +411,10 @@ public class File implements Serializable, Comparable<File> { * if an I/O error occurs. */ public String getCanonicalPath() throws IOException { - return realpath(getAbsolutePath()); + return canonicalizePath(getAbsolutePath()); } - /** - * TODO: move this stuff to libcore.os. - * @hide - */ - private static native String realpath(String path); - private static native String readlink(String path); + private static native String canonicalizePath(String path); /** * Returns a new file created using the canonical path of this file. diff --git a/luni/src/main/java/java/io/FileDescriptor.java b/luni/src/main/java/java/io/FileDescriptor.java index e4eb06c..eba0e4d 100644 --- a/luni/src/main/java/java/io/FileDescriptor.java +++ b/luni/src/main/java/java/io/FileDescriptor.java @@ -17,9 +17,9 @@ package java.io; -import libcore.io.ErrnoException; +import android.system.ErrnoException; import libcore.io.Libcore; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * Wraps a Unix file descriptor. It's possible to get the file descriptor used by some @@ -105,6 +105,15 @@ public final class FileDescriptor { this.descriptor = fd; } + /** + * @hide internal use only + */ + public boolean isSocket() { + return isSocket(descriptor); + } + + private static native boolean isSocket(int fd); + @Override public String toString() { return "FileDescriptor[" + descriptor + "]"; } diff --git a/luni/src/main/java/java/io/FileInputStream.java b/luni/src/main/java/java/io/FileInputStream.java index b2e620f..7944ef1 100644 --- a/luni/src/main/java/java/io/FileInputStream.java +++ b/luni/src/main/java/java/io/FileInputStream.java @@ -19,15 +19,13 @@ package java.io; import dalvik.system.CloseGuard; -import java.nio.NioUtils; +import android.system.ErrnoException; import java.nio.channels.FileChannel; -import java.util.Arrays; -import libcore.io.ErrnoException; +import java.nio.NioUtils; import libcore.io.IoBridge; -import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.Streams; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * An input stream that reads bytes from a file. @@ -75,7 +73,7 @@ public class FileInputStream extends InputStream { if (file == null) { throw new NullPointerException("file == null"); } - this.fd = IoBridge.open(file.getAbsolutePath(), O_RDONLY); + this.fd = IoBridge.open(file.getPath(), O_RDONLY); this.shouldClose = true; guard.open("close"); } @@ -118,7 +116,7 @@ public class FileInputStream extends InputStream { channel.close(); } if (shouldClose) { - IoUtils.close(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } else { // An owned fd has been invalidated by IoUtils.close, but // we need to explicitly stop using an unowned fd (http://b/4361076). diff --git a/luni/src/main/java/java/io/FileOutputStream.java b/luni/src/main/java/java/io/FileOutputStream.java index e04ab5f..f91ee20 100644 --- a/luni/src/main/java/java/io/FileOutputStream.java +++ b/luni/src/main/java/java/io/FileOutputStream.java @@ -20,10 +20,9 @@ package java.io; import dalvik.system.CloseGuard; import java.nio.NioUtils; import java.nio.channels.FileChannel; -import java.util.Arrays; import libcore.io.IoBridge; -import libcore.io.IoUtils; -import static libcore.io.OsConstants.*; + +import static android.system.OsConstants.*; /** * An output stream that writes bytes to a file. If the output file exists, it @@ -85,7 +84,7 @@ public class FileOutputStream extends OutputStream { throw new NullPointerException("file == null"); } this.mode = O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC); - this.fd = IoBridge.open(file.getAbsolutePath(), mode); + this.fd = IoBridge.open(file.getPath(), mode); this.shouldClose = true; this.guard.open("close"); } @@ -136,7 +135,7 @@ public class FileOutputStream extends OutputStream { channel.close(); } if (shouldClose) { - IoUtils.close(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } else { // An owned fd has been invalidated by IoUtils.close, but // we need to explicitly stop using an unowned fd (http://b/4361076). diff --git a/luni/src/main/java/java/io/InputStream.java b/luni/src/main/java/java/io/InputStream.java index 973382e..c489b2a 100644 --- a/luni/src/main/java/java/io/InputStream.java +++ b/luni/src/main/java/java/io/InputStream.java @@ -209,19 +209,21 @@ public abstract class InputStream extends Object implements Closeable { } /** - * Skips at most {@code n} bytes in this stream. This method does nothing and returns - * 0 if {@code n} is negative, but some subclasses may throw. + * Skips at most {@code byteCount} bytes in this stream. The number of actual + * bytes skipped may be anywhere between 0 and {@code byteCount}. If + * {@code byteCount} is negative, this method does nothing and returns 0, but + * some subclasses may throw. * - * <p>Note the "at most" in the description of this method: this method may choose to skip - * fewer bytes than requested. Callers should <i>always</i> check the return value. + * <p>Note the "at most" in the description of this method: this method may + * choose to skip fewer bytes than requested. Callers should <i>always</i> + * check the return value. * - * <p>This default implementation reads bytes into a temporary - * buffer. Concrete subclasses should provide their own implementation. + * <p>This default implementation reads bytes into a temporary buffer. Concrete + * subclasses should provide their own implementation. * - * @param byteCount the number of bytes to skip. * @return the number of bytes actually skipped. - * @throws IOException - * if this stream is closed or another IOException occurs. + * @throws IOException if this stream is closed or another IOException + * occurs. */ public long skip(long byteCount) throws IOException { return Streams.skipByReading(this, byteCount); diff --git a/luni/src/main/java/java/io/InputStreamReader.java b/luni/src/main/java/java/io/InputStreamReader.java index 01e7f29..d57b916 100644 --- a/luni/src/main/java/java/io/InputStreamReader.java +++ b/luni/src/main/java/java/io/InputStreamReader.java @@ -23,8 +23,6 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; -import java.nio.charset.MalformedInputException; -import java.nio.charset.UnmappableCharacterException; import java.util.Arrays; /** @@ -260,7 +258,9 @@ public class InputStreamReader extends Reader { if (result == CoderResult.UNDERFLOW && endOfInput) { result = decoder.decode(bytes, out, true); - decoder.flush(out); + if (result == CoderResult.UNDERFLOW) { + result = decoder.flush(out); + } decoder.reset(); } if (result.isMalformed() || result.isUnmappable()) { diff --git a/luni/src/main/java/java/io/ObjectInputStream.java b/luni/src/main/java/java/io/ObjectInputStream.java index 17a6974..3a89b52 100644 --- a/luni/src/main/java/java/io/ObjectInputStream.java +++ b/luni/src/main/java/java/io/ObjectInputStream.java @@ -1739,9 +1739,7 @@ public class ObjectInputStream extends InputStream implements ObjectInput, Objec */ protected Class<?> resolveProxyClass(String[] interfaceNames) throws IOException, ClassNotFoundException { - // TODO: This method is opportunity for performance enhancement - // We can cache the classloader and recently used interfaces. - ClassLoader loader = ClassLoader.getSystemClassLoader(); + ClassLoader loader = callerClassLoader; Class<?>[] interfaces = new Class<?>[interfaceNames.length]; for (int i = 0; i < interfaceNames.length; i++) { interfaces[i] = Class.forName(interfaceNames[i], false, loader); diff --git a/luni/src/main/java/java/io/ObjectOutput.java b/luni/src/main/java/java/io/ObjectOutput.java index 2e454ec..e6d7b0c 100644 --- a/luni/src/main/java/java/io/ObjectOutput.java +++ b/luni/src/main/java/java/io/ObjectOutput.java @@ -18,7 +18,7 @@ package java.io; /** - * Defines an interface for classes that allow reading serialized objects. + * Defines an interface for classes that allow writing serialized objects. * * @see ObjectOutputStream * @see ObjectInput diff --git a/luni/src/main/java/java/io/ObjectOutputStream.java b/luni/src/main/java/java/io/ObjectOutputStream.java index 6a2fbed..f67919d 100644 --- a/luni/src/main/java/java/io/ObjectOutputStream.java +++ b/luni/src/main/java/java/io/ObjectOutputStream.java @@ -39,15 +39,13 @@ import libcore.io.SizeOf; */ public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants { - private static final Class<?>[] WRITE_UNSHARED_PARAM_TYPES = new Class[] { Object.class }; - /* * Mask to zero SC_BLOC_DATA bit. */ private static final byte NOT_SC_BLOCK_DATA = (byte) (SC_BLOCK_DATA ^ 0xFF); /* - * How many nested levels to writeObject. We may not need this. + * How many nested levels to writeObject. */ private int nestedLevels; @@ -98,11 +96,6 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob private int protocolVersion; /* - * Used to detect nested exception when saving an exception due to an error - */ - private StreamCorruptedException nestedException; - - /* * Used to keep track of the PutField object for the class/object being * written */ @@ -271,7 +264,6 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob this.subclassOverridingImplementation = false; resetState(); - this.nestedException = new StreamCorruptedException(); // So write...() methods can be used by // subclasses during writeStreamHeader() primitiveTypes = this.output; @@ -462,20 +454,6 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob output.flush(); } - /* - * These methods get the value of a field named fieldName of object - * instance. The field is declared by declaringClass. The field is the same - * type as the method return value. - * - * these methods could be implemented non-natively on top of - * java.lang.reflect at the expense of extra object creation - * (java.lang.reflect.Field). Otherwise Serialization could not fetch - * private fields, except by the use of a native method like this one. - * - * @throws NoSuchFieldError If the field does not exist. - */ - private static native Object getFieldL(Object instance, Class<?> declaringClass, String fieldName, String fieldTypeName); - /** * Return the next handle to be used to indicate cyclic * references being saved to the stream. @@ -1236,7 +1214,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob // The handle for the classDesc is NOT the handle for the class object // being dumped. We must allocate a new handle and return it. if (clDesc.isEnum()) { - writeEnumDesc(object, clDesc, unshared); + writeEnumDesc(clDesc, unshared); } else { writeClassDesc(clDesc, unshared); } @@ -1521,14 +1499,15 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob primitiveTypes = output; } } catch (IOException ioEx1) { - // This will make it pass through until the top caller. It also - // lets it pass through the nested exception. - if (nestedLevels == 0 && ioEx1 != nestedException) { + // This will make it pass through until the top caller. Only the top caller writes the + // exception (where it can). + if (nestedLevels == 0) { try { writeNewException(ioEx1); } catch (IOException ioEx2) { - nestedException.fillInStackTrace(); - throw nestedException; + // If writing the exception to the output stream causes another exception there + // is no need to propagate the second exception or generate a third exception, + // both of which might obscure details of the root cause. } } throw ioEx1; // and then we propagate the original exception @@ -1563,9 +1542,8 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob writeNull(); return -1; } - int handle = -1; if (!unshared) { - handle = dumpCycle(object); + int handle = dumpCycle(object); if (handle != -1) { return handle; // cyclic reference } @@ -1592,7 +1570,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob if (clDesc.isSerializable() && computeClassBasedReplacement) { if (clDesc.hasMethodWriteReplace()){ Method methodWriteReplace = clDesc.getMethodWriteReplace(); - Object replObj = null; + Object replObj; try { replObj = methodWriteReplace.invoke(object, (Object[]) null); } catch (IllegalAccessException iae) { @@ -1677,7 +1655,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob } // write for Enum Class Desc only, which is different from other classes - private ObjectStreamClass writeEnumDesc(Class<?> theClass, ObjectStreamClass classDesc, boolean unshared) + private ObjectStreamClass writeEnumDesc(ObjectStreamClass classDesc, boolean unshared) throws IOException { // write classDesc, classDesc for enum is different @@ -1716,7 +1694,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob if (superClassDesc != null) { // super class is also enum superClassDesc.setFlags((byte) (SC_SERIALIZABLE | SC_ENUM)); - writeEnumDesc(superClassDesc.forClass(), superClassDesc, unshared); + writeEnumDesc(superClassDesc, unshared); } else { output.writeByte(TC_NULL); } @@ -1740,7 +1718,7 @@ public class ObjectOutputStream extends OutputStream implements ObjectOutput, Ob theClass = theClass.getSuperclass(); } ObjectStreamClass classDesc = ObjectStreamClass.lookup(theClass); - writeEnumDesc(theClass, classDesc, unshared); + writeEnumDesc(classDesc, unshared); int previousHandle = -1; if (unshared) { diff --git a/luni/src/main/java/java/io/OutputStreamWriter.java b/luni/src/main/java/java/io/OutputStreamWriter.java index d69c87a..bc8710d 100644 --- a/luni/src/main/java/java/io/OutputStreamWriter.java +++ b/luni/src/main/java/java/io/OutputStreamWriter.java @@ -122,8 +122,8 @@ public class OutputStreamWriter extends Writer { } /** - * Closes this writer. This implementation flushes the buffer as well as the - * target stream. The target stream is then closed and the resources for the + * Closes this writer. This implementation flushes the buffer but <strong>does not</strong> + * flush the target stream. The target stream is then closed and the resources for the * buffer and converter are released. * * <p>Only the first invocation of this method has any effect. Subsequent calls diff --git a/luni/src/main/java/java/io/PipedInputStream.java b/luni/src/main/java/java/io/PipedInputStream.java index 2c27695..3e81cb8 100644 --- a/luni/src/main/java/java/io/PipedInputStream.java +++ b/luni/src/main/java/java/io/PipedInputStream.java @@ -390,6 +390,7 @@ public class PipedInputStream extends InputStream { if (lastReader != null && !lastReader.isAlive()) { throw new IOException("Pipe broken"); } + notifyAll(); wait(1000); } @@ -402,6 +403,10 @@ public class PipedInputStream extends InputStream { if (in == -1) { in = 0; } + if (lastReader != null && !lastReader.isAlive()) { + throw new IOException("Pipe broken"); + } + buffer[in++] = (byte) oneByte; if (in == buffer.length) { in = 0; diff --git a/luni/src/main/java/java/io/PipedOutputStream.java b/luni/src/main/java/java/io/PipedOutputStream.java index 1b139e9..4c431c1 100644 --- a/luni/src/main/java/java/io/PipedOutputStream.java +++ b/luni/src/main/java/java/io/PipedOutputStream.java @@ -140,7 +140,7 @@ public class PipedOutputStream extends OutputStream { * @throws IOException * if this stream is not connected, if the target stream is * closed or if the thread reading from the target stream is no - * longer alive. This case is currently not handled correctly. + * longer alive. */ @Override public void write(byte[] buffer, int offset, int count) throws IOException { diff --git a/luni/src/main/java/java/io/PipedReader.java b/luni/src/main/java/java/io/PipedReader.java index 908505d..7bf9ed4 100644 --- a/luni/src/main/java/java/io/PipedReader.java +++ b/luni/src/main/java/java/io/PipedReader.java @@ -33,7 +33,7 @@ public class PipedReader extends Reader { private Thread lastWriter; - private boolean isClosed; + boolean isClosed; /** * The circular buffer through which data is passed. Data is read from the diff --git a/luni/src/main/java/java/io/PipedWriter.java b/luni/src/main/java/java/io/PipedWriter.java index ad8974b..e85f69c 100644 --- a/luni/src/main/java/java/io/PipedWriter.java +++ b/luni/src/main/java/java/io/PipedWriter.java @@ -17,8 +17,6 @@ package java.io; -import java.util.Arrays; - /** * Places information on a communications pipe. When two threads want to pass * data back and forth, one creates a piped writer and the other creates a piped @@ -115,6 +113,9 @@ public class PipedWriter extends Writer { } synchronized (reader) { + if (reader.isClosed) { + throw new IOException("Pipe is broken"); + } reader.notifyAll(); } } diff --git a/luni/src/main/java/java/io/RandomAccessFile.java b/luni/src/main/java/java/io/RandomAccessFile.java index a60da3e..da99765 100644 --- a/luni/src/main/java/java/io/RandomAccessFile.java +++ b/luni/src/main/java/java/io/RandomAccessFile.java @@ -17,19 +17,18 @@ package java.io; +import android.system.ErrnoException; import dalvik.system.CloseGuard; import java.nio.ByteOrder; -import java.nio.NioUtils; import java.nio.channels.FileChannel; import java.nio.charset.ModifiedUtf8; +import java.nio.NioUtils; import java.util.Arrays; -import libcore.io.ErrnoException; import libcore.io.IoBridge; -import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.Memory; import libcore.io.SizeOf; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * Allows reading from and writing to a file in a random-access manner. This is @@ -115,7 +114,7 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable { throw new IllegalArgumentException("Invalid mode: " + mode); } this.mode = flags; - this.fd = IoBridge.open(file.getAbsolutePath(), flags); + this.fd = IoBridge.open(file.getPath(), flags); // if we are in "rws" mode, attempt to sync file+metadata if (syncMetadata) { @@ -163,7 +162,7 @@ public class RandomAccessFile implements DataInput, DataOutput, Closeable { channel.close(); channel = null; } - IoUtils.close(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } } diff --git a/luni/src/main/java/java/lang/AbstractStringBuilder.java b/luni/src/main/java/java/lang/AbstractStringBuilder.java index c3107f2..4d84078 100644 --- a/luni/src/main/java/java/lang/AbstractStringBuilder.java +++ b/luni/src/main/java/java/lang/AbstractStringBuilder.java @@ -17,9 +17,10 @@ package java.lang; +import libcore.util.EmptyArray; + import java.io.InvalidObjectException; import java.util.Arrays; -import libcore.util.EmptyArray; /** * A modifiable {@link CharSequence sequence of characters} for use in creating @@ -192,14 +193,8 @@ abstract class AbstractStringBuilder { } /** - * Retrieves the character at the {@code index}. - * - * @param index - * the index of the character to retrieve. - * @return the char value. - * @throws IndexOutOfBoundsException - * if {@code index} is negative or greater than or equal to the - * current {@link #length()}. + * Returns the character at {@code index}. + * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= length()}. */ public char charAt(int index) { if (index < 0 || index >= count) { @@ -217,50 +212,46 @@ abstract class AbstractStringBuilder { } final void delete0(int start, int end) { - if (start >= 0) { - if (end > count) { - end = count; - } - if (end == start) { - return; - } - if (end > start) { - int length = count - end; - if (length >= 0) { - if (!shared) { - System.arraycopy(value, end, value, start, length); - } else { - char[] newData = new char[value.length]; - System.arraycopy(value, 0, newData, 0, start); - System.arraycopy(value, end, newData, start, length); - value = newData; - shared = false; - } - } - count -= end - start; - return; - } + // NOTE: StringBuilder#delete(int, int) is specified not to throw if + // the end index is >= count, as long as it's >= start. This means + // we have to clamp it to count here. + if (end > count) { + end = count; } - throw startEndAndLength(start, end); - } - final void deleteCharAt0(int index) { - if (index < 0 || index >= count) { - throw indexAndLength(index); + if (start < 0 || start > count || start > end) { + throw startEndAndLength(start, end); } - int length = count - index - 1; - if (length > 0) { + + // NOTE: StringBuilder#delete(int, int) throws only if start > count + // (start == count is considered valid, oddly enough). Since 'end' is + // already a clamped value, that case is handled here. + if (end == start) { + return; + } + + // At this point we know for sure that end > start. + int length = count - end; + if (length >= 0) { if (!shared) { - System.arraycopy(value, index + 1, value, index, length); + System.arraycopy(value, end, value, start, length); } else { char[] newData = new char[value.length]; - System.arraycopy(value, 0, newData, 0, index); - System.arraycopy(value, index + 1, newData, index, length); + System.arraycopy(value, 0, newData, 0, start); + System.arraycopy(value, end, newData, start, length); value = newData; shared = false; } } - count--; + count -= end - start; + } + + final void deleteCharAt0(int index) { + if (index < 0 || index >= count) { + throw indexAndLength(index); + } + + delete0(index, index + 1); } /** @@ -552,7 +543,7 @@ abstract class AbstractStringBuilder { * * @param length * the new length of this StringBuffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code length < 0}. * @see #length */ diff --git a/luni/src/main/java/java/lang/CaseMapper.java b/luni/src/main/java/java/lang/CaseMapper.java index 5ec41f5..1da621c 100644 --- a/luni/src/main/java/java/lang/CaseMapper.java +++ b/luni/src/main/java/java/lang/CaseMapper.java @@ -18,6 +18,7 @@ package java.lang; import java.util.Locale; import libcore.icu.ICU; +import libcore.icu.Transliterator; /** * Performs case operations as described by http://unicode.org/reports/tr21/tr21-5.html. @@ -45,9 +46,10 @@ class CaseMapper { */ public static String toLowerCase(Locale locale, String s, char[] value, int offset, int count) { // Punt hard cases to ICU4C. + // Note that Greek isn't a particularly hard case for toLowerCase, only toUpperCase. String languageCode = locale.getLanguage(); if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) { - return ICU.toLowerCase(s, locale.toString()); + return ICU.toLowerCase(s, locale); } char[] newValue = null; @@ -57,7 +59,7 @@ class CaseMapper { char newCh; if (ch == LATIN_CAPITAL_I_WITH_DOT || Character.isHighSurrogate(ch)) { // Punt these hard cases. - return ICU.toLowerCase(s, locale.toString()); + return ICU.toLowerCase(s, locale); } else if (ch == GREEK_CAPITAL_SIGMA && isFinalSigma(value, offset, count, i)) { newCh = GREEK_SMALL_FINAL_SIGMA; } else { @@ -139,10 +141,19 @@ class CaseMapper { return index; } + private static final ThreadLocal<Transliterator> EL_UPPER = new ThreadLocal<Transliterator>() { + @Override protected Transliterator initialValue() { + return new Transliterator("el-Upper"); + } + }; + public static String toUpperCase(Locale locale, String s, char[] value, int offset, int count) { String languageCode = locale.getLanguage(); if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) { - return ICU.toUpperCase(s, locale.toString()); + return ICU.toUpperCase(s, locale); + } + if (languageCode.equals("el")) { + return EL_UPPER.get().transliterate(s); } char[] output = null; @@ -150,7 +161,7 @@ class CaseMapper { for (int o = offset, end = offset + count; o < end; o++) { char ch = value[o]; if (Character.isHighSurrogate(ch)) { - return ICU.toUpperCase(s, locale.toString()); + return ICU.toUpperCase(s, locale); } int index = upperIndex(ch); if (index == -1) { diff --git a/luni/src/main/java/java/lang/CharSequence.java b/luni/src/main/java/java/lang/CharSequence.java index fc1ecd3..0e1ea3f 100644 --- a/luni/src/main/java/java/lang/CharSequence.java +++ b/luni/src/main/java/java/lang/CharSequence.java @@ -32,15 +32,8 @@ public interface CharSequence { public int length(); /** - * Returns the character at the specified index, with the first character - * having index zero. - * - * @param index - * the index of the character to return. - * @return the requested character. - * @throws IndexOutOfBoundsException - * if {@code index < 0} or {@code index} is greater than the - * length of this sequence. + * Returns the character at {@code index}. + * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= length()}. */ public char charAt(int index); diff --git a/luni/src/main/java/java/lang/Character.java b/luni/src/main/java/java/lang/Character.java index 5762bd4..2046ba8 100644 --- a/luni/src/main/java/java/lang/Character.java +++ b/luni/src/main/java/java/lang/Character.java @@ -46,7 +46,7 @@ import java.util.Arrays; * point or a UTF-16 unit that's part of a surrogate pair. The {@code int} type * is used to represent all Unicode code points. * - * <a name="unicode_categories"><h3>Unicode categories</h3></a> + * <a name="unicode_categories"></a><h3>Unicode categories</h3> * <p>Here's a list of the Unicode character categories and the corresponding Java constant, * grouped semantically to provide a convenient overview. This table is also useful in * conjunction with {@code \p} and {@code \P} in {@link java.util.regex.Pattern regular expressions}. @@ -1489,7 +1489,7 @@ public final class Character implements Serializable, Comparable<Character> { if (blockName == null) { throw new NullPointerException("blockName == null"); } - int block = forNameImpl(blockName); + int block = unicodeBlockForName(blockName); if (block == -1) { throw new IllegalArgumentException("Unknown block: " + blockName); } @@ -1510,7 +1510,7 @@ public final class Character implements Serializable, Comparable<Character> { */ public static UnicodeBlock of(int codePoint) { checkValidCodePoint(codePoint); - int block = ofImpl(codePoint); + int block = unicodeBlockForCodePoint(codePoint); if (block == -1 || block >= BLOCKS.length) { return null; } @@ -1522,9 +1522,14 @@ public final class Character implements Serializable, Comparable<Character> { } } - private static native int forNameImpl(String blockName); + private static native int unicodeBlockForName(String blockName); + + private static native int unicodeBlockForCodePoint(int codePoint); + + private static native int unicodeScriptForName(String blockName); + + private static native int unicodeScriptForCodePoint(int codePoint); - private static native int ofImpl(int codePoint); /** * Constructs a new {@code Character} with the specified primitive char @@ -2525,25 +2530,28 @@ public final class Character implements Serializable, Comparable<Character> { } /** - * Gets the Unicode directionality of the specified character. - * - * @param codePoint - * the Unicode code point to get the directionality of. - * @return the Unicode directionality of {@code codePoint}. + * Returns the Unicode directionality of the given code point. + * This will be one of the {@code DIRECTIONALITY_} constants. + * For characters whose directionality is undefined, or whose + * directionality has no appropriate constant in this class, + * {@code DIRECTIONALITY_UNDEFINED} is returned. */ public static byte getDirectionality(int codePoint) { if (getType(codePoint) == Character.UNASSIGNED) { return Character.DIRECTIONALITY_UNDEFINED; } - byte directionality = getDirectionalityImpl(codePoint); - if (directionality == -1) { - return -1; + byte directionality = getIcuDirectionality(codePoint); + if (directionality >= 0 && directionality < DIRECTIONALITY.length) { + return DIRECTIONALITY[directionality]; } - return DIRECTIONALITY[directionality]; + return Character.DIRECTIONALITY_UNDEFINED; } - private static native byte getDirectionalityImpl(int codePoint); + /** + * @hide - internal use only. + */ + public static native byte getIcuDirectionality(int codePoint); /** * Indicates whether the specified character is mirrored. diff --git a/luni/src/main/java/java/lang/Double.java b/luni/src/main/java/java/lang/Double.java index 456529b..cb8c301 100644 --- a/luni/src/main/java/java/lang/Double.java +++ b/luni/src/main/java/java/lang/Double.java @@ -171,7 +171,13 @@ public final class Double extends Number implements Comparable<Double> { * {@code value}. All <em>Not-a-Number (NaN)</em> values are converted to a single NaN * representation ({@code 0x7ff8000000000000L}) (compare to {@link #doubleToRawLongBits}). */ - public static native long doubleToLongBits(double value); + public static long doubleToLongBits(double value) { + if (value != value) { + return 0x7ff8000000000000L; // NaN. + } else { + return doubleToRawLongBits(value); + } + } /** * Returns an integer corresponding to the bits of the given diff --git a/luni/src/main/java/java/lang/Float.java b/luni/src/main/java/java/lang/Float.java index 900b2a0..5f316f1 100644 --- a/luni/src/main/java/java/lang/Float.java +++ b/luni/src/main/java/java/lang/Float.java @@ -199,7 +199,13 @@ public final class Float extends Number implements Comparable<Float> { * float {@code value}. All <em>Not-a-Number (NaN)</em> values are converted to a single NaN * representation ({@code 0x7fc00000}) (compare to {@link #floatToRawIntBits}). */ - public static native int floatToIntBits(float value); + public static int floatToIntBits(float value) { + if (value != value) { + return 0x7fc00000; // NaN. + } else { + return floatToRawIntBits(value); + } + } /** * Returns an integer corresponding to the bits of the given diff --git a/luni/src/main/java/java/lang/Integer.java b/luni/src/main/java/java/lang/Integer.java index fc38b41..8ae0312 100644 --- a/luni/src/main/java/java/lang/Integer.java +++ b/luni/src/main/java/java/lang/Integer.java @@ -126,7 +126,8 @@ public final class Integer extends Number implements Comparable<Integer> { /** * Compares two {@code int} values. - * @return 0 if lhs = rhs, less than 0 if lhs < rhs, and greater than 0 if lhs > rhs. + * @return 0 if lhs = rhs, less than 0 if lhs < rhs, and greater than 0 + * if lhs > rhs. * @since 1.7 */ public static int compare(int lhs, int rhs) { @@ -140,24 +141,26 @@ public final class Integer extends Number implements Comparable<Integer> { /** * Parses the specified string and returns a {@code Integer} instance if the * string can be decoded into an integer value. The string may be an - * optional minus sign "-" followed by a hexadecimal ("0x..." or "#..."), - * octal ("0..."), or decimal ("...") representation of an integer. + * optional sign character ("-" or "+") followed by a hexadecimal ("0x..." + * or "#..."), octal ("0..."), or decimal ("...") representation of an + * integer. * * @param string * a string representation of an integer value. * @return an {@code Integer} containing the value represented by * {@code string}. * @throws NumberFormatException - * if {@code string} cannot be parsed as an integer value. + * if {@code string} cannot be parsed as an integer value. */ public static Integer decode(String string) throws NumberFormatException { - int length = string.length(), i = 0; + int length = string.length(); if (length == 0) { throw invalidInt(string); } + int i = 0; char firstDigit = string.charAt(i); boolean negative = firstDigit == '-'; - if (negative) { + if (negative || firstDigit == '+') { if (length == 1) { throw invalidInt(string); } @@ -319,8 +322,8 @@ public final class Integer extends Number implements Comparable<Integer> { /** * Parses the specified string as a signed decimal integer value. The ASCII - * character \u002d ('-') is recognized as the minus sign. - * + * characters \u002d ('-') and \u002b ('+') are recognized as the minus and + * plus signs. * @param string * the string representation of an integer value. * @return the primitive integer value represented by {@code string}. @@ -333,7 +336,8 @@ public final class Integer extends Number implements Comparable<Integer> { /** * Parses the specified string as a signed integer value using the specified - * radix. The ASCII character \u002d ('-') is recognized as the minus sign. + * radix. The ASCII characters \u002d ('-') and \u002b ('+') are recognized + * as the minus and plus signs. * * @param string * the string representation of an integer value. @@ -350,24 +354,56 @@ public final class Integer extends Number implements Comparable<Integer> { if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { throw new NumberFormatException("Invalid radix: " + radix); } - if (string == null) { + if (string == null || string.isEmpty()) { throw invalidInt(string); } - int length = string.length(), i = 0; - if (length == 0) { + + char firstChar = string.charAt(0); + int firstDigitIndex = (firstChar == '-' || firstChar == '+') ? 1 : 0; + if (firstDigitIndex == string.length()) { throw invalidInt(string); } - boolean negative = string.charAt(i) == '-'; - if (negative && ++i == length) { + + return parse(string, firstDigitIndex, radix, firstChar == '-'); + } + + /** + * Equivalent to {@code parsePositiveInt(string, 10)}. + * + * @see #parsePositiveInt(String, int) + * + * @hide + */ + public static int parsePositiveInt(String string) throws NumberFormatException { + return parsePositiveInt(string, 10); + } + + /** + * Parses the specified string as a positive integer value using the + * specified radix. 0 is considered a positive integer. + * <p> + * This method behaves the same as {@link #parseInt(String, int)} except + * that it disallows leading '+' and '-' characters. See that method for + * error conditions. + * + * @see #parseInt(String, int) + * + * @hide + */ + public static int parsePositiveInt(String string, int radix) throws NumberFormatException { + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new NumberFormatException("Invalid radix: " + radix); + } + if (string == null || string.length() == 0) { throw invalidInt(string); } - - return parse(string, i, radix, negative); + return parse(string, 0, radix, false); } private static int parse(String string, int offset, int radix, boolean negative) throws NumberFormatException { int max = Integer.MIN_VALUE / radix; - int result = 0, length = string.length(); + int result = 0; + int length = string.length(); while (offset < length) { int digit = Character.digit(string.charAt(offset++), radix); if (digit == -1) { diff --git a/luni/src/main/java/java/lang/Long.java b/luni/src/main/java/java/lang/Long.java index 84169af..5c11564 100644 --- a/luni/src/main/java/java/lang/Long.java +++ b/luni/src/main/java/java/lang/Long.java @@ -127,8 +127,8 @@ public final class Long extends Number implements Comparable<Long> { /** * Parses the specified string and returns a {@code Long} instance if the * string can be decoded into a long value. The string may be an optional - * minus sign "-" followed by a hexadecimal ("0x..." or "#..."), octal - * ("0..."), or decimal ("...") representation of a long. + * optional sign character ("-" or "+") followed by a hexadecimal ("0x..." + * or "#..."), octal ("0..."), or decimal ("...") representation of a long. * * @param string * a string representation of a long value. @@ -137,13 +137,15 @@ public final class Long extends Number implements Comparable<Long> { * if {@code string} cannot be parsed as a long value. */ public static Long decode(String string) throws NumberFormatException { - int length = string.length(), i = 0; + int length = string.length(); if (length == 0) { throw invalidLong(string); } + + int i = 0; char firstDigit = string.charAt(i); boolean negative = firstDigit == '-'; - if (negative) { + if (negative || firstDigit == '+') { if (length == 1) { throw invalidLong(string); } @@ -306,7 +308,8 @@ public final class Long extends Number implements Comparable<Long> { /** * Parses the specified string as a signed decimal long value. The ASCII - * character \u002d ('-') is recognized as the minus sign. + * characters \u002d ('-') and \u002b ('+') are recognized as the minus and + * plus signs. * * @param string * the string representation of a long value. @@ -320,7 +323,8 @@ public final class Long extends Number implements Comparable<Long> { /** * Parses the specified string as a signed long value using the specified - * radix. The ASCII character \u002d ('-') is recognized as the minus sign. + * radix. The ASCII characters \u002d ('-') and \u002b ('+') are recognized + * as the minus and plus signs. * * @param string * the string representation of a long value. @@ -337,24 +341,22 @@ public final class Long extends Number implements Comparable<Long> { if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { throw new NumberFormatException("Invalid radix: " + radix); } - if (string == null) { - throw invalidLong(string); - } - int length = string.length(), i = 0; - if (length == 0) { + if (string == null || string.isEmpty()) { throw invalidLong(string); } - boolean negative = string.charAt(i) == '-'; - if (negative && ++i == length) { + char firstChar = string.charAt(0); + int firstDigitIndex = (firstChar == '-' || firstChar == '+') ? 1 : 0; + if (firstDigitIndex == string.length()) { throw invalidLong(string); } - return parse(string, i, radix, negative); + return parse(string, firstDigitIndex, radix, firstChar == '-'); } private static long parse(String string, int offset, int radix, boolean negative) { long max = Long.MIN_VALUE / radix; - long result = 0, length = string.length(); + long result = 0; + int length = string.length(); while (offset < length) { int digit = Character.digit(string.charAt(offset++), radix); if (digit == -1) { @@ -378,6 +380,39 @@ public final class Long extends Number implements Comparable<Long> { return result; } + /** + * Equivalent to {@code parsePositiveLong(string, 10)}. + * + * @see #parsePositiveLong(String, int) + * + * @hide + */ + public static long parsePositiveLong(String string) throws NumberFormatException { + return parsePositiveLong(string, 10); + } + + /** + * Parses the specified string as a positive long value using the + * specified radix. 0 is considered a positive long. + * <p> + * This method behaves the same as {@link #parseLong(String, int)} except + * that it disallows leading '+' and '-' characters. See that method for + * error conditions. + * + * @see #parseLong(String, int) + * + * @hide + */ + public static long parsePositiveLong(String string, int radix) throws NumberFormatException { + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new NumberFormatException("Invalid radix: " + radix); + } + if (string == null || string.length() == 0) { + throw invalidLong(string); + } + return parse(string, 0, radix, false); + } + @Override public short shortValue() { return (short) value; diff --git a/luni/src/main/java/java/lang/ProcessManager.java b/luni/src/main/java/java/lang/ProcessManager.java index 28314b7..ec87fda 100644 --- a/luni/src/main/java/java/lang/ProcessManager.java +++ b/luni/src/main/java/java/lang/ProcessManager.java @@ -16,23 +16,23 @@ package java.lang; +import android.system.ErrnoException; +import android.util.MutableInt; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; +import java.io.IOException; import java.io.OutputStream; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import libcore.io.ErrnoException; import libcore.io.IoUtils; import libcore.io.Libcore; -import libcore.util.MutableInt; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * Manages child processes. diff --git a/luni/src/main/java/java/lang/Runtime.java b/luni/src/main/java/java/lang/Runtime.java index 8538f8a..a3cb83e 100644 --- a/luni/src/main/java/java/lang/Runtime.java +++ b/luni/src/main/java/java/lang/Runtime.java @@ -48,7 +48,7 @@ import java.util.StringTokenizer; import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.util.EmptyArray; -import static libcore.io.OsConstants._SC_NPROCESSORS_CONF; +import static android.system.OsConstants._SC_NPROCESSORS_CONF; /** * Allows Java applications to interface with the environment in which they are @@ -307,57 +307,64 @@ public class Runtime { } /** - * Loads and links the dynamic library that is identified through the - * specified path. This method is similar to {@link #loadLibrary(String)}, - * but it accepts a full path specification whereas {@code loadLibrary} just - * accepts the name of the library to load. + * Loads the shared library found at the given absolute path. + * This should be of the form {@code /path/to/library/libMyLibrary.so}. + * Most callers should use {@link #loadLibrary(String)} instead, and + * let the system find the correct file to load. * - * @param pathName - * the absolute (platform dependent) path to the library to load. - * @throws UnsatisfiedLinkError - * if the library can not be loaded. + * @throws UnsatisfiedLinkError if the library can not be loaded, + * either because it's not found or because there is something wrong with it. */ - public void load(String pathName) { - load(pathName, VMStack.getCallingClassLoader()); + public void load(String absolutePath) { + load(absolutePath, VMStack.getCallingClassLoader()); } /* - * Loads and links the given library without security checks. + * Loads the given shared library using the given ClassLoader. */ - void load(String pathName, ClassLoader loader) { - if (pathName == null) { - throw new NullPointerException("pathName == null"); + void load(String absolutePath, ClassLoader loader) { + if (absolutePath == null) { + throw new NullPointerException("absolutePath == null"); } - String error = doLoad(pathName, loader); + String error = doLoad(absolutePath, loader); if (error != null) { throw new UnsatisfiedLinkError(error); } } /** - * Loads and links the library with the specified name. The mapping of the - * specified library name to the full path for loading the library is - * implementation-dependent. + * Loads a shared library. Class loaders have some influence over this + * process, but for a typical Android app, it works as follows: * - * @param libName - * the name of the library to load. - * @throws UnsatisfiedLinkError - * if the library can not be loaded. + * <p>Given the name {@code "MyLibrary"}, that string will be passed to + * {@link System#mapLibraryName}. That means it would be a mistake + * for the caller to include the usual {@code "lib"} prefix and {@code ".so"} + * suffix. + * + * <p>That file will then be searched for on the application's native library + * search path. This consists of the application's own native library directory + * followed by the system's native library directories. + * + * @throws UnsatisfiedLinkError if the library can not be loaded, + * either because it's not found or because there is something wrong with it. */ - public void loadLibrary(String libName) { - loadLibrary(libName, VMStack.getCallingClassLoader()); + public void loadLibrary(String nickname) { + loadLibrary(nickname, VMStack.getCallingClassLoader()); } /* - * Searches for a library, then loads and links it without security checks. + * Searches for and loads the given shared library using the given ClassLoader. */ void loadLibrary(String libraryName, ClassLoader loader) { if (loader != null) { String filename = loader.findLibrary(libraryName); if (filename == null) { - throw new UnsatisfiedLinkError("Couldn't load " + libraryName + - " from loader " + loader + - ": findLibrary returned 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) + "\""); } String error = doLoad(filename, loader); if (error != null) { diff --git a/luni/src/main/java/java/lang/StringToReal.java b/luni/src/main/java/java/lang/StringToReal.java index 97f6d6b..4bd4fb1 100644 --- a/luni/src/main/java/java/lang/StringToReal.java +++ b/luni/src/main/java/java/lang/StringToReal.java @@ -122,20 +122,20 @@ final class StringToReal { result.e = -result.e; } } catch (NumberFormatException ex) { - // We already checked the string, so the exponent must have been out of range for an int. + // We already checked the string, so the exponent must have been out of range for an + // int. if (negativeExponent) { result.zero = true; } else { result.infinity = true; } - return result; + // Fall through: We want to check the content of the mantissa and throw an + // exception if it contains invalid characters. For example: "JUNK" * 10^12 should + // be treated as an error, not as infinity. } } else { end = length; } - if (length == 0) { - throw invalidReal(s, isDouble); - } int start = 0; c = s.charAt(start); @@ -151,7 +151,19 @@ final class StringToReal { throw invalidReal(s, isDouble); } - int decimal = s.indexOf('.'); + // Confirm that the mantissa should parse. + int decimal = -1; + for (int i = start; i < end; i++) { + char mc = s.charAt(i); + if (mc == '.') { + if (decimal != -1) { + throw invalidReal(s, isDouble); + } + decimal = i; + } else if (mc < '0' || mc > '9') { + throw invalidReal(s, isDouble); + } + } if (decimal > -1) { result.e -= end - decimal - 1; s = s.substring(start, decimal) + s.substring(decimal + 1, end); @@ -159,10 +171,17 @@ final class StringToReal { s = s.substring(start, end); } - if ((length = s.length()) == 0) { + length = s.length(); + if (length == 0) { throw invalidReal(s, isDouble); } + // All syntactic checks that might throw an exception are above. If we have established + // one of the non-exception error conditions we can stop here. + if (result.infinity || result.zero) { + return result; + } + end = length; while (end > 1 && s.charAt(end - 1) == '0') { --end; @@ -237,7 +256,7 @@ final class StringToReal { * the String that will be parsed to a floating point * @return the double closest to the real number * - * @exception NumberFormatException + * @throws NumberFormatException * if the String doesn't represent a double */ public static double parseDouble(String s) { @@ -278,7 +297,7 @@ final class StringToReal { * the String that will be parsed to a floating point * @return the float closest to the real number * - * @exception NumberFormatException + * @throws NumberFormatException * if the String doesn't represent a float */ public static float parseFloat(String s) { diff --git a/luni/src/main/java/java/lang/System.java b/luni/src/main/java/java/lang/System.java index 68ca506..55ca762 100644 --- a/luni/src/main/java/java/lang/System.java +++ b/luni/src/main/java/java/lang/System.java @@ -32,6 +32,9 @@ package java.lang; +import android.system.ErrnoException; +import android.system.StructPasswd; +import android.system.StructUtsname; import dalvik.system.VMRuntime; import dalvik.system.VMStack; import java.io.BufferedInputStream; @@ -39,8 +42,8 @@ import java.io.Console; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; +import java.io.IOException; import java.io.PrintStream; import java.nio.channels.Channel; import java.nio.channels.spi.SelectorProvider; @@ -51,11 +54,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import libcore.icu.ICU; -import libcore.io.ErrnoException; import libcore.io.Libcore; -import libcore.io.StructPasswd; -import libcore.io.StructUtsname; -import libcore.util.ZoneInfoDB; /** * Provides access to system-related information and resources including @@ -83,12 +82,31 @@ public final class System { public static final PrintStream err; private static final String lineSeparator; + private static final Properties unchangeableSystemProperties; private static Properties systemProperties; + /** + * Dedicated lock for GC / Finalization logic. + */ + private static final Object lock = new Object(); + + /** + * Whether or not we need to do a GC before running the finalizers. + */ + private static boolean runGC; + + /** + * If we just ran finalization, we might want to do a GC to free the finalized objects. + * This lets us do gc/runFinlization/gc sequences but prevents back to back System.gc(). + */ + private static boolean justRanFinalization; + static { err = new PrintStream(new FileOutputStream(FileDescriptor.err)); out = new PrintStream(new FileOutputStream(FileDescriptor.out)); in = new BufferedInputStream(new FileInputStream(FileDescriptor.in)); + unchangeableSystemProperties = initUnchangeableSystemProperties(); + systemProperties = createSystemProperties(); lineSeparator = System.getProperty("line.separator"); } @@ -153,7 +171,433 @@ public final class System { * @param length * the number of elements to be copied. */ - public static native void arraycopy(Object src, int srcPos, Object dst, int dstPos, int length); + + public static native void arraycopy(Object src, int srcPos, + Object dst, int dstPos, int length); + + /** + * The char array length threshold below which to use a Java + * (non-native) version of arraycopy() instead of the native + * version. See b/7103825. + */ + private static final int ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD = 32; + + /** + * The char[] specialized version of arraycopy(). + * + * @hide internal use only + */ + public static void arraycopy(char[] src, int srcPos, char[] dst, int dstPos, int length) { + if (src == null) { + throw new NullPointerException("src == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + if (srcPos < 0 || dstPos < 0 || length < 0 || + srcPos > src.length - length || dstPos > dst.length - length) { + throw new ArrayIndexOutOfBoundsException( + "src.length=" + src.length + " srcPos=" + srcPos + + " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length); + } + if (length <= ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD) { + // Copy char by char for shorter arrays. + if (src == dst && srcPos < dstPos && dstPos < srcPos + length) { + // Copy backward (to avoid overwriting elements before + // they are copied in case of an overlap on the same + // array.) + for (int i = length - 1; i >= 0; --i) { + dst[dstPos + i] = src[srcPos + i]; + } + } else { + // Copy forward. + for (int i = 0; i < length; ++i) { + dst[dstPos + i] = src[srcPos + i]; + } + } + } else { + // Call the native version for longer arrays. + arraycopyCharUnchecked(src, srcPos, dst, dstPos, length); + } + } + + /** + * The char[] specialized, unchecked, native version of + * arraycopy(). This assumes error checking has been done. + */ + private static native void arraycopyCharUnchecked(char[] src, int srcPos, + char[] dst, int dstPos, int length); + + /** + * The byte array length threshold below which to use a Java + * (non-native) version of arraycopy() instead of the native + * version. See b/7103825. + */ + private static final int ARRAYCOPY_SHORT_BYTE_ARRAY_THRESHOLD = 32; + + /** + * The byte[] specialized version of arraycopy(). + * + * @hide internal use only + */ + public static void arraycopy(byte[] src, int srcPos, byte[] dst, int dstPos, int length) { + if (src == null) { + throw new NullPointerException("src == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + if (srcPos < 0 || dstPos < 0 || length < 0 || + srcPos > src.length - length || dstPos > dst.length - length) { + throw new ArrayIndexOutOfBoundsException( + "src.length=" + src.length + " srcPos=" + srcPos + + " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length); + } + if (length <= ARRAYCOPY_SHORT_BYTE_ARRAY_THRESHOLD) { + // Copy byte by byte for shorter arrays. + if (src == dst && srcPos < dstPos && dstPos < srcPos + length) { + // Copy backward (to avoid overwriting elements before + // they are copied in case of an overlap on the same + // array.) + for (int i = length - 1; i >= 0; --i) { + dst[dstPos + i] = src[srcPos + i]; + } + } else { + // Copy forward. + for (int i = 0; i < length; ++i) { + dst[dstPos + i] = src[srcPos + i]; + } + } + } else { + // Call the native version for longer arrays. + arraycopyByteUnchecked(src, srcPos, dst, dstPos, length); + } + } + + /** + * The byte[] specialized, unchecked, native version of + * arraycopy(). This assumes error checking has been done. + */ + private static native void arraycopyByteUnchecked(byte[] src, int srcPos, + byte[] dst, int dstPos, int length); + + /** + * The short array length threshold below which to use a Java + * (non-native) version of arraycopy() instead of the native + * version. See b/7103825. + */ + private static final int ARRAYCOPY_SHORT_SHORT_ARRAY_THRESHOLD = 32; + + /** + * The short[] specialized version of arraycopy(). + * + * @hide internal use only + */ + public static void arraycopy(short[] src, int srcPos, short[] dst, int dstPos, int length) { + if (src == null) { + throw new NullPointerException("src == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + if (srcPos < 0 || dstPos < 0 || length < 0 || + srcPos > src.length - length || dstPos > dst.length - length) { + throw new ArrayIndexOutOfBoundsException( + "src.length=" + src.length + " srcPos=" + srcPos + + " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length); + } + if (length <= ARRAYCOPY_SHORT_SHORT_ARRAY_THRESHOLD) { + // Copy short by short for shorter arrays. + if (src == dst && srcPos < dstPos && dstPos < srcPos + length) { + // Copy backward (to avoid overwriting elements before + // they are copied in case of an overlap on the same + // array.) + for (int i = length - 1; i >= 0; --i) { + dst[dstPos + i] = src[srcPos + i]; + } + } else { + // Copy forward. + for (int i = 0; i < length; ++i) { + dst[dstPos + i] = src[srcPos + i]; + } + } + } else { + // Call the native version for longer arrays. + arraycopyShortUnchecked(src, srcPos, dst, dstPos, length); + } + } + + /** + * The short[] specialized, unchecked, native version of + * arraycopy(). This assumes error checking has been done. + */ + private static native void arraycopyShortUnchecked(short[] src, int srcPos, + short[] dst, int dstPos, int length); + + /** + * The short array length threshold below which to use a Java + * (non-native) version of arraycopy() instead of the native + * version. See b/7103825. + */ + private static final int ARRAYCOPY_SHORT_INT_ARRAY_THRESHOLD = 32; + + /** + * The int[] specialized version of arraycopy(). + * + * @hide internal use only + */ + public static void arraycopy(int[] src, int srcPos, int[] dst, int dstPos, int length) { + if (src == null) { + throw new NullPointerException("src == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + if (srcPos < 0 || dstPos < 0 || length < 0 || + srcPos > src.length - length || dstPos > dst.length - length) { + throw new ArrayIndexOutOfBoundsException( + "src.length=" + src.length + " srcPos=" + srcPos + + " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length); + } + if (length <= ARRAYCOPY_SHORT_INT_ARRAY_THRESHOLD) { + // Copy int by int for shorter arrays. + if (src == dst && srcPos < dstPos && dstPos < srcPos + length) { + // Copy backward (to avoid overwriting elements before + // they are copied in case of an overlap on the same + // array.) + for (int i = length - 1; i >= 0; --i) { + dst[dstPos + i] = src[srcPos + i]; + } + } else { + // Copy forward. + for (int i = 0; i < length; ++i) { + dst[dstPos + i] = src[srcPos + i]; + } + } + } else { + // Call the native version for longer arrays. + arraycopyIntUnchecked(src, srcPos, dst, dstPos, length); + } + } + + /** + * The int[] specialized, unchecked, native version of + * arraycopy(). This assumes error checking has been done. + */ + private static native void arraycopyIntUnchecked(int[] src, int srcPos, + int[] dst, int dstPos, int length); + + /** + * The short array length threshold below which to use a Java + * (non-native) version of arraycopy() instead of the native + * version. See b/7103825. + */ + private static final int ARRAYCOPY_SHORT_LONG_ARRAY_THRESHOLD = 32; + + /** + * The long[] specialized version of arraycopy(). + * + * @hide internal use only + */ + public static void arraycopy(long[] src, int srcPos, long[] dst, int dstPos, int length) { + if (src == null) { + throw new NullPointerException("src == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + if (srcPos < 0 || dstPos < 0 || length < 0 || + srcPos > src.length - length || dstPos > dst.length - length) { + throw new ArrayIndexOutOfBoundsException( + "src.length=" + src.length + " srcPos=" + srcPos + + " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length); + } + if (length <= ARRAYCOPY_SHORT_LONG_ARRAY_THRESHOLD) { + // Copy long by long for shorter arrays. + if (src == dst && srcPos < dstPos && dstPos < srcPos + length) { + // Copy backward (to avoid overwriting elements before + // they are copied in case of an overlap on the same + // array.) + for (int i = length - 1; i >= 0; --i) { + dst[dstPos + i] = src[srcPos + i]; + } + } else { + // Copy forward. + for (int i = 0; i < length; ++i) { + dst[dstPos + i] = src[srcPos + i]; + } + } + } else { + // Call the native version for longer arrays. + arraycopyLongUnchecked(src, srcPos, dst, dstPos, length); + } + } + + /** + * The long[] specialized, unchecked, native version of + * arraycopy(). This assumes error checking has been done. + */ + private static native void arraycopyLongUnchecked(long[] src, int srcPos, + long[] dst, int dstPos, int length); + + /** + * The short array length threshold below which to use a Java + * (non-native) version of arraycopy() instead of the native + * version. See b/7103825. + */ + private static final int ARRAYCOPY_SHORT_FLOAT_ARRAY_THRESHOLD = 32; + + /** + * The float[] specialized version of arraycopy(). + * + * @hide internal use only + */ + public static void arraycopy(float[] src, int srcPos, float[] dst, int dstPos, int length) { + if (src == null) { + throw new NullPointerException("src == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + if (srcPos < 0 || dstPos < 0 || length < 0 || + srcPos > src.length - length || dstPos > dst.length - length) { + throw new ArrayIndexOutOfBoundsException( + "src.length=" + src.length + " srcPos=" + srcPos + + " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length); + } + if (length <= ARRAYCOPY_SHORT_FLOAT_ARRAY_THRESHOLD) { + // Copy float by float for shorter arrays. + if (src == dst && srcPos < dstPos && dstPos < srcPos + length) { + // Copy backward (to avoid overwriting elements before + // they are copied in case of an overlap on the same + // array.) + for (int i = length - 1; i >= 0; --i) { + dst[dstPos + i] = src[srcPos + i]; + } + } else { + // Copy forward. + for (int i = 0; i < length; ++i) { + dst[dstPos + i] = src[srcPos + i]; + } + } + } else { + // Call the native version for floater arrays. + arraycopyFloatUnchecked(src, srcPos, dst, dstPos, length); + } + } + + /** + * The float[] specialized, unchecked, native version of + * arraycopy(). This assumes error checking has been done. + */ + private static native void arraycopyFloatUnchecked(float[] src, int srcPos, + float[] dst, int dstPos, int length); + + /** + * The short array length threshold below which to use a Java + * (non-native) version of arraycopy() instead of the native + * version. See b/7103825. + */ + private static final int ARRAYCOPY_SHORT_DOUBLE_ARRAY_THRESHOLD = 32; + + /** + * The double[] specialized version of arraycopy(). + * + * @hide internal use only + */ + public static void arraycopy(double[] src, int srcPos, double[] dst, int dstPos, int length) { + if (src == null) { + throw new NullPointerException("src == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + if (srcPos < 0 || dstPos < 0 || length < 0 || + srcPos > src.length - length || dstPos > dst.length - length) { + throw new ArrayIndexOutOfBoundsException( + "src.length=" + src.length + " srcPos=" + srcPos + + " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length); + } + if (length <= ARRAYCOPY_SHORT_DOUBLE_ARRAY_THRESHOLD) { + // Copy double by double for shorter arrays. + if (src == dst && srcPos < dstPos && dstPos < srcPos + length) { + // Copy backward (to avoid overwriting elements before + // they are copied in case of an overlap on the same + // array.) + for (int i = length - 1; i >= 0; --i) { + dst[dstPos + i] = src[srcPos + i]; + } + } else { + // Copy forward. + for (int i = 0; i < length; ++i) { + dst[dstPos + i] = src[srcPos + i]; + } + } + } else { + // Call the native version for floater arrays. + arraycopyDoubleUnchecked(src, srcPos, dst, dstPos, length); + } + } + + /** + * The double[] specialized, unchecked, native version of + * arraycopy(). This assumes error checking has been done. + */ + private static native void arraycopyDoubleUnchecked(double[] src, int srcPos, + double[] dst, int dstPos, int length); + + /** + * The short array length threshold below which to use a Java + * (non-native) version of arraycopy() instead of the native + * version. See b/7103825. + */ + private static final int ARRAYCOPY_SHORT_BOOLEAN_ARRAY_THRESHOLD = 32; + + /** + * The boolean[] specialized version of arraycopy(). + * + * @hide internal use only + */ + public static void arraycopy(boolean[] src, int srcPos, boolean[] dst, int dstPos, int length) { + if (src == null) { + throw new NullPointerException("src == null"); + } + if (dst == null) { + throw new NullPointerException("dst == null"); + } + if (srcPos < 0 || dstPos < 0 || length < 0 || + srcPos > src.length - length || dstPos > dst.length - length) { + throw new ArrayIndexOutOfBoundsException( + "src.length=" + src.length + " srcPos=" + srcPos + + " dst.length=" + dst.length + " dstPos=" + dstPos + " length=" + length); + } + if (length <= ARRAYCOPY_SHORT_BOOLEAN_ARRAY_THRESHOLD) { + // Copy boolean by boolean for shorter arrays. + if (src == dst && srcPos < dstPos && dstPos < srcPos + length) { + // Copy backward (to avoid overwriting elements before + // they are copied in case of an overlap on the same + // array.) + for (int i = length - 1; i >= 0; --i) { + dst[dstPos + i] = src[srcPos + i]; + } + } else { + // Copy forward. + for (int i = 0; i < length; ++i) { + dst[dstPos + i] = src[srcPos + i]; + } + } + } else { + // Call the native version for floater arrays. + arraycopyBooleanUnchecked(src, srcPos, dst, dstPos, length); + } + } + + /** + * The boolean[] specialized, unchecked, native version of + * arraycopy(). This assumes error checking has been done. + */ + private static native void arraycopyBooleanUnchecked(boolean[] src, int srcPos, + boolean[] dst, int dstPos, int length); /** * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. @@ -196,7 +640,18 @@ public final class System { * that the garbage collector will actually be run. */ public static void gc() { - Runtime.getRuntime().gc(); + boolean shouldRunGC; + synchronized(lock) { + shouldRunGC = justRanFinalization; + if (shouldRunGC) { + justRanFinalization = false; + } else { + runGC = true; + } + } + if (shouldRunGC) { + Runtime.getRuntime().gc(); + } } /** @@ -246,13 +701,10 @@ public final class System { * @return the system properties. */ public static Properties getProperties() { - if (systemProperties == null) { - initSystemProperties(); - } return systemProperties; } - private static void initSystemProperties() { + private static Properties initUnchangeableSystemProperties() { VMRuntime runtime = VMRuntime.getRuntime(); Properties p = new Properties(); @@ -277,15 +729,6 @@ public final class System { } p.put("java.home", javaHome); - // On Android, each app gets its own temporary directory. This is just a fallback - // default, useful only on the host. - p.put("java.io.tmpdir", "/tmp"); - - String ldLibraryPath = getenv("LD_LIBRARY_PATH"); - if (ldLibraryPath != null) { - p.put("java.library.path", ldLibraryPath); - } - p.put("java.specification.name", "Dalvik Core Library"); p.put("java.specification.vendor", projectName); p.put("java.specification.version", "0.9"); @@ -313,7 +756,6 @@ public final class System { try { StructPasswd passwd = Libcore.os.getpwuid(Libcore.os.getuid()); - p.put("user.home", passwd.pw_dir); p.put("user.name", passwd.pw_name); } catch (ErrnoException exception) { throw new AssertionError(exception); @@ -333,8 +775,36 @@ public final class System { // Override built-in properties with settings from the command line. parsePropertyAssignments(p, runtime.properties()); + return p; + } + + /** + * Inits an unchangeable system property with the given value. + * This is useful when the environment needs to change under native bridge emulation. + */ + private static void initUnchangeableSystemProperty(String name, String value) { + checkPropertyName(name); + unchangeableSystemProperties.put(name, value); + } + + private static void setDefaultChangeableProperties(Properties p) { + // On Android, each app gets its own temporary directory. + // (See android.app.ActivityThread.) This is just a fallback default, + // useful only on the host. + p.put("java.io.tmpdir", "/tmp"); - systemProperties = p; + // Android has always had an empty "user.home" (see docs for getProperty). + // This is not useful for normal android apps which need to use android specific + // APIs such as {@code Context.getFilesDir} and {@code Context.getCacheDir} but + // we make it changeable for backward compatibility, so that they can change it + // to a writeable location if required. + p.put("user.home", ""); + } + + private static Properties createSystemProperties() { + Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableSystemProperties); + setDefaultChangeableProperties(p); + return p; } /** @@ -360,7 +830,7 @@ public final class System { * Returns the value of a particular system property or {@code null} if no * such property exists. * - * <p>The following properties are always provided by the Dalvik VM: + * <p>The following properties are always provided by the Dalvik VM:</p> * <p><table BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0" SUMMARY=""> * <tr BGCOLOR="#CCCCFF" CLASS="TableHeadingColor"> * <td><b>Name</b></td> <td><b>Meaning</b></td> <td><b>Example</b></td></tr> @@ -401,7 +871,8 @@ public final class System { * * </table> * - * <p>It is a mistake to try to override any of these. Doing so will have unpredictable results. + * <p> All of the above properties except for {@code user.home} and {@code java.io.tmpdir} + * <b>cannot be modified</b>. Any attempt to change them will be a no-op. * * @param propertyName * the name of the system property to look up. @@ -418,22 +889,26 @@ public final class System { */ public static String getProperty(String name, String defaultValue) { checkPropertyName(name); - return getProperties().getProperty(name, defaultValue); + return systemProperties.getProperty(name, defaultValue); } /** - * Sets the value of a particular system property. + * Sets the value of a particular system property. Most system properties + * are read only and cannot be cleared or modified. See {@link #setProperty} for a + * list of such properties. * * @return the old value of the property or {@code null} if the property * didn't exist. */ public static String setProperty(String name, String value) { checkPropertyName(name); - return (String) getProperties().setProperty(name, value); + return (String) systemProperties.setProperty(name, value); } /** - * Removes a specific system property. + * Removes a specific system property. Most system properties + * are read only and cannot be cleared or modified. See {@link #setProperty} for a + * list of such properties. * * @return the property value or {@code null} if the property didn't exist. * @throws NullPointerException @@ -443,7 +918,7 @@ public final class System { */ public static String clearProperty(String name) { checkPropertyName(name); - return (String) getProperties().remove(name); + return (String) systemProperties.remove(name); } private static void checkPropertyName(String name) { @@ -500,27 +975,14 @@ public final class System { } /** - * Loads and links the dynamic library that is identified through the - * specified path. This method is similar to {@link #loadLibrary(String)}, - * but it accepts a full path specification whereas {@code loadLibrary} just - * accepts the name of the library to load. - * - * @param pathName - * the path of the file to be loaded. + * See {@link Runtime#load}. */ public static void load(String pathName) { Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader()); } /** - * Loads and links the library with the specified name. The mapping of the - * specified library name to the full path for loading the library is - * implementation-dependent. - * - * @param libName - * the name of the library to load. - * @throws UnsatisfiedLinkError - * if the library could not be loaded. + * See {@link Runtime#loadLibrary}. */ public static void loadLibrary(String libName) { Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader()); @@ -575,7 +1037,18 @@ public final class System { * to perform any outstanding object finalization. */ public static void runFinalization() { + boolean shouldRunGC; + synchronized(lock) { + shouldRunGC = runGC; + runGC = false; + } + if (shouldRunGC) { + Runtime.getRuntime().gc(); + } Runtime.getRuntime().runFinalization(); + synchronized(lock) { + justRanFinalization = true; + } } /** @@ -594,12 +1067,21 @@ public final class System { } /** - * Sets all system properties. This does not take a copy; the passed-in object is used - * directly. Passing null causes the VM to reinitialize the properties to how they were - * when the VM was started. + * Attempts to set all system properties. Copies all properties from + * {@code p} and discards system properties that are read only and cannot + * be modified. See {@link #setProperty} for a list of such properties. */ public static void setProperties(Properties p) { - systemProperties = p; + PropertiesWithNonOverrideableDefaults userProperties = + new PropertiesWithNonOverrideableDefaults(unchangeableSystemProperties); + if (p != null) { + userProperties.putAll(p); + } else { + // setProperties(null) is documented to restore defaults. + setDefaultChangeableProperties(userProperties); + } + + systemProperties = userProperties; } /** @@ -621,25 +1103,46 @@ public final class System { /** * Returns the platform specific file name format for the shared library - * named by the argument. - * - * @param userLibName - * the name of the library to look up. - * @return the platform specific filename for the library. + * named by the argument. On Android, this would turn {@code "MyLibrary"} into + * {@code "libMyLibrary.so"}. */ - public static native String mapLibraryName(String userLibName); + public static native String mapLibraryName(String nickname); /** - * Sets the value of the named static field in the receiver to the passed in - * argument. - * - * @param fieldName - * the name of the field to set, one of in, out, or err - * @param stream - * the new value of the field + * Used to set System.err, System.in, and System.out. */ - private static native void setFieldImpl(String fieldName, String signature, Object stream); + private static native void setFieldImpl(String field, String signature, Object stream); + /** + * A properties class that prohibits changes to any of the properties + * contained in its defaults. + */ + static final class PropertiesWithNonOverrideableDefaults extends Properties { + PropertiesWithNonOverrideableDefaults(Properties defaults) { + super(defaults); + } + + @Override + public Object put(Object key, Object value) { + if (defaults.containsKey(key)) { + logE("Ignoring attempt to set property \"" + key + + "\" to value \"" + value + "\"."); + return defaults.get(key); + } + + return super.put(key, value); + } + + @Override + public Object remove(Object key) { + if (defaults.containsKey(key)) { + logE("Ignoring attempt to remove property \"" + key + "\"."); + return null; + } + + return super.remove(key); + } + } /** * The unmodifiable environment variables map. System.getenv() specifies diff --git a/luni/src/main/java/java/lang/ref/FinalizerReference.java b/luni/src/main/java/java/lang/ref/FinalizerReference.java index 14eaae4..5416a80 100644 --- a/luni/src/main/java/java/lang/ref/FinalizerReference.java +++ b/luni/src/main/java/java/lang/ref/FinalizerReference.java @@ -83,12 +83,18 @@ public final class FinalizerReference<T> extends Reference<T> { * Waits for all currently-enqueued references to be finalized. */ public static void finalizeAllEnqueued() throws InterruptedException { - Sentinel sentinel = new Sentinel(); - enqueueSentinelReference(sentinel); + // Alloate a new sentinel, this creates a FinalizerReference. + Sentinel sentinel; + // Keep looping until we safely enqueue our sentinel FinalizerReference. + // This is done to prevent races where the GC updates the pendingNext + // before we get the chance. + do { + sentinel = new Sentinel(); + } while (!enqueueSentinelReference(sentinel)); sentinel.awaitFinalization(); } - private static void enqueueSentinelReference(Sentinel sentinel) { + private static boolean enqueueSentinelReference(Sentinel sentinel) { synchronized (LIST_LOCK) { // When a finalizable object is allocated, a FinalizerReference is added to the list. // We search the list for that FinalizerReference (it should be at or near the head), @@ -98,8 +104,20 @@ public final class FinalizerReference<T> extends Reference<T> { FinalizerReference<Sentinel> sentinelReference = (FinalizerReference<Sentinel>) r; sentinelReference.referent = null; sentinelReference.zombie = sentinel; - sentinelReference.enqueueInternal(); - return; + // Make a single element list, then enqueue the reference on the daemon unenqueued + // list. This is required instead of enqueuing directly on the finalizer queue + // since there could be recently freed objects in the unqueued list which are not + // yet on the finalizer queue. This could cause the sentinel to run before the + // objects are finalized. b/17381967 + // Make circular list if unenqueued goes through native so that we can prevent + // races where the GC updates the pendingNext before we do. If it is non null, then + // we update the pending next to make a circular list while holding a lock. + // b/17462553 + if (!sentinelReference.makeCircularListIfUnenqueued()) { + return false; + } + ReferenceQueue.add(sentinelReference); + return true; } } } @@ -108,6 +126,8 @@ public final class FinalizerReference<T> extends Reference<T> { throw new AssertionError("newly-created live Sentinel not on list!"); } + private native boolean makeCircularListIfUnenqueued(); + /** * A marker object that we can immediately enqueue. When this object's * finalize() method is called, we know all previously-enqueued finalizable diff --git a/luni/src/main/java/java/lang/ref/Reference.java b/luni/src/main/java/java/lang/ref/Reference.java deleted file mode 100644 index bd63535..0000000 --- a/luni/src/main/java/java/lang/ref/Reference.java +++ /dev/null @@ -1,200 +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. - */ -/* - * 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 java.lang.ref; - -/** - * Provides an abstract class which describes behavior common to all reference - * objects. It is not possible to create immediate subclasses of - * {@code Reference} in addition to the ones provided by this package. It is - * also not desirable to do so, since references require very close cooperation - * with the system's garbage collector. The existing, specialized reference - * classes should be used instead. - * - * <p>Three different type of references exist, each being weaker than the preceding one: - * {@link java.lang.ref.SoftReference}, {@link java.lang.ref.WeakReference}, and - * {@link java.lang.ref.PhantomReference}. "Weakness" here means that less restrictions are - * being imposed on the garbage collector as to when it is allowed to - * actually garbage-collect the referenced object. - * - * <p>In order to use reference objects properly it is important to understand - * the different types of reachability that trigger their clearing and - * enqueueing. The following table lists these, from strongest to weakest. - * For each row, an object is said to have the reachability on the left side - * if (and only if) it fulfills all of the requirements on the right side. In - * all rows, consider the <em>root set</em> to be a set of references that - * are "resistant" to garbage collection (that is, running threads, method - * parameters, local variables, static fields and the like). - * - * <p><table> - * <tr> - * <td>Strongly reachable</td> - * <td> <ul> - * <li>There exists at least one path from the root set to the object that does not traverse any - * instance of a {@code java.lang.ref.Reference} subclass. - * </li> - * </ul> </td> - * </tr> - * - * <tr> - * <td>Softly reachable</td> - * <td> <ul> - * <li>The object is not strongly reachable.</li> - * <li>There exists at least one path from the root set to the object that does traverse - * a {@code java.lang.ref.SoftReference} instance, but no {@code java.lang.ref.WeakReference} - * or {@code java.lang.ref.PhantomReference} instances.</li> - * </ul> </td> - * </tr> - * - * <tr> - * <td>Weakly reachable</td> - * <td> <ul> - * <li>The object is neither strongly nor softly reachable.</li> - * <li>There exists at least one path from the root set to the object that does traverse a - * {@code java.lang.ref.WeakReference} instance, but no {@code java.lang.ref.PhantomReference} - * instances.</li> - * </ul> </td> - * </tr> - * - * <tr> - * <td>Phantom-reachable</td> - * <td> <ul> - * <li>The object is neither strongly, softly, nor weakly reachable.</li> - * <li>The object is referenced by a {@code java.lang.ref.PhantomReference} instance.</li> - * <li>The object has already been finalized.</li> - * </ul> </td> - * </tr> - * </table> - */ -public abstract class Reference<T> { - - /** - * The object to which this reference refers. - * VM requirement: this field <em>must</em> be called "referent" - * and be an object. - */ - volatile T referent; - - /** - * If non-null, the queue on which this reference will be enqueued - * when the referent is appropriately reachable. - * VM requirement: this field <em>must</em> be called "queue" - * and be a java.lang.ref.ReferenceQueue. - */ - volatile ReferenceQueue<? super T> queue; - - /** - * Used internally by java.lang.ref.ReferenceQueue. - * VM requirement: this field <em>must</em> be called "queueNext" - * and be a java.lang.ref.Reference. - */ - @SuppressWarnings("unchecked") - volatile Reference queueNext; - - /** - * Used internally by the VM. This field forms a circular and - * singly linked list of reference objects discovered by the - * garbage collector and awaiting processing by the reference - * queue thread. - * - * @hide - */ - public volatile Reference<?> pendingNext; - - /** - * Constructs a new instance of this class. - */ - Reference() { - } - - Reference(T r, ReferenceQueue<? super T> q) { - referent = r; - queue = q; - } - - /** - * Makes the referent {@code null}. This does not force the reference - * object to be enqueued. - */ - public void clear() { - referent = null; - } - - /** - * Adds an object to its reference queue. - * - * @return {@code true} if this call has caused the {@code Reference} to - * become enqueued, or {@code false} otherwise - * - * @hide - */ - public final synchronized boolean enqueueInternal() { - if (queue != null && queueNext == null) { - queue.enqueue(this); - queue = null; - return true; - } - return false; - } - - /** - * Forces the reference object to be enqueued if it has been associated with - * a queue. - * - * @return {@code true} if this call has caused the {@code Reference} to - * become enqueued, or {@code false} otherwise - */ - public boolean enqueue() { - return enqueueInternal(); - } - - /** - * Returns the referent of the reference object. - * - * @return the referent to which reference refers, or {@code null} if the - * object has been cleared. - */ - public T get() { - return referent; - } - - /** - * Checks whether the reference object has been enqueued. - * - * @return {@code true} if the {@code Reference} has been enqueued, {@code - * false} otherwise - */ - public boolean isEnqueued() { - return queueNext != null; - } - -} diff --git a/luni/src/main/java/java/lang/ref/ReferenceQueue.java b/luni/src/main/java/java/lang/ref/ReferenceQueue.java index 2b8089c..4c78fbf 100644 --- a/luni/src/main/java/java/lang/ref/ReferenceQueue.java +++ b/luni/src/main/java/java/lang/ref/ReferenceQueue.java @@ -28,6 +28,7 @@ public class ReferenceQueue<T> { private static final int NANOS_PER_MILLI = 1000000; private Reference<? extends T> head; + private Reference<? extends T> tail; /** * Constructs a new instance of this class. @@ -48,18 +49,16 @@ public class ReferenceQueue<T> { return null; } - Reference<? extends T> ret; + Reference<? extends T> ret = head; - ret = head; - - if (head == head.queueNext) { + if (head == tail) { + tail = null; head = null; } else { head = head.queueNext; } ret.queueNext = null; - return ret; } @@ -133,12 +132,16 @@ public class ReferenceQueue<T> { * reference object to be enqueued. */ synchronized void enqueue(Reference<? extends T> reference) { - if (head == null) { - reference.queueNext = reference; + if (tail == null) { + head = reference; } else { - reference.queueNext = head; + tail.queueNext = reference; } - head = reference; + + // The newly enqueued reference becomes the new tail, and always + // points to itself. + tail = reference; + tail.queueNext = reference; notify(); } @@ -150,9 +153,18 @@ public class ReferenceQueue<T> { if (unenqueued == null) { unenqueued = list; } else { - Reference<?> next = unenqueued.pendingNext; - unenqueued.pendingNext = list.pendingNext; - list.pendingNext = next; + // Find the last element in unenqueued. + Reference<?> last = unenqueued; + while (last.pendingNext != unenqueued) { + last = last.pendingNext; + } + // Add our list to the end. Update the pendingNext to point back to enqueued. + last.pendingNext = list; + last = list; + while (last.pendingNext != list) { + last = last.pendingNext; + } + last.pendingNext = unenqueued; } ReferenceQueue.class.notifyAll(); } diff --git a/luni/src/main/java/java/lang/reflect/Array.java b/luni/src/main/java/java/lang/reflect/Array.java index 088a434..a7dacfe 100644 --- a/luni/src/main/java/java/lang/reflect/Array.java +++ b/luni/src/main/java/java/lang/reflect/Array.java @@ -352,16 +352,16 @@ public final class Array { public static Object newInstance(Class<?> componentType, int size) throws NegativeArraySizeException { if (!componentType.isPrimitive()) { return createObjectArray(componentType, size); - } else if (componentType == boolean.class) { - return new boolean[size]; - } else if (componentType == byte.class) { - return new byte[size]; } else if (componentType == char.class) { return new char[size]; - } else if (componentType == short.class) { - return new short[size]; } else if (componentType == int.class) { return new int[size]; + } else if (componentType == byte.class) { + return new byte[size]; + } else if (componentType == boolean.class) { + return new boolean[size]; + } else if (componentType == short.class) { + return new short[size]; } else if (componentType == long.class) { return new long[size]; } else if (componentType == float.class) { diff --git a/luni/src/main/java/java/lang/reflect/Modifier.java b/luni/src/main/java/java/lang/reflect/Modifier.java index 5f973d5..257064e 100644 --- a/luni/src/main/java/java/lang/reflect/Modifier.java +++ b/luni/src/main/java/java/lang/reflect/Modifier.java @@ -106,7 +106,7 @@ public class Modifier { * require they implement. * @hide */ - public static final int MIRANDA = 0x8000; + public static final int MIRANDA = 0x200000; /** * Dex addition to mark instance constructors and static class diff --git a/luni/src/main/java/java/math/BigDecimal.java b/luni/src/main/java/java/math/BigDecimal.java index 03ce8dd..f735607 100644 --- a/luni/src/main/java/java/math/BigDecimal.java +++ b/luni/src/main/java/java/math/BigDecimal.java @@ -1025,6 +1025,14 @@ public class BigDecimal extends Number implements Comparable<BigDecimal>, Serial } long diffScale = ((long)this.scale - divisor.scale) - scale; + + // Check whether the diffScale will fit into an int. See http://b/17393664. + if (bitLength(diffScale) > 32) { + throw new ArithmeticException( + "Unable to perform divisor / dividend scaling: the difference in scale is too" + + " big (" + diffScale + ")"); + } + if(this.bitLength < 64 && divisor.bitLength < 64 ) { if(diffScale == 0) { return dividePrimitiveLongs(this.smallValue, diff --git a/luni/src/main/java/java/math/Multiplication.java b/luni/src/main/java/java/math/Multiplication.java index 98cabee..093b1b7 100644 --- a/luni/src/main/java/java/math/Multiplication.java +++ b/luni/src/main/java/java/math/Multiplication.java @@ -125,49 +125,45 @@ class Multiplication { } else if (exp <= 50) { // To calculate: 10^exp return BigInteger.TEN.pow(intExp); - } else if (exp <= 1000) { - // To calculate: 5^exp * 2^exp - return bigFivePows[1].pow(intExp).shiftLeft(intExp); } - // "LARGE POWERS" - /* - * To check if there is free memory to allocate a BigInteger of the - * estimated size, measured in bytes: 1 + [exp / log10(2)] - */ - long byteArraySize = 1 + (long)(exp / 2.4082399653118496); - - if (byteArraySize > Runtime.getRuntime().freeMemory()) { - throw new ArithmeticException(); - } - if (exp <= Integer.MAX_VALUE) { - // To calculate: 5^exp * 2^exp - return bigFivePows[1].pow(intExp).shiftLeft(intExp); - } - /* - * "HUGE POWERS" - * - * This branch probably won't be executed since the power of ten is too - * big. - */ - // To calculate: 5^exp - BigInteger powerOfFive = bigFivePows[1].pow(Integer.MAX_VALUE); - BigInteger res = powerOfFive; - long longExp = exp - Integer.MAX_VALUE; - - intExp = (int)(exp % Integer.MAX_VALUE); - while (longExp > Integer.MAX_VALUE) { - res = res.multiply(powerOfFive); - longExp -= Integer.MAX_VALUE; - } - res = res.multiply(bigFivePows[1].pow(intExp)); - // To calculate: 5^exp << exp - res = res.shiftLeft(Integer.MAX_VALUE); - longExp = exp - Integer.MAX_VALUE; - while (longExp > Integer.MAX_VALUE) { - res = res.shiftLeft(Integer.MAX_VALUE); - longExp -= Integer.MAX_VALUE; + + BigInteger res = null; + try { + // "LARGE POWERS" + if (exp <= Integer.MAX_VALUE) { + // To calculate: 5^exp * 2^exp + res = bigFivePows[1].pow(intExp).shiftLeft(intExp); + } else { + /* + * "HUGE POWERS" + * + * This branch probably won't be executed since the power of ten is too + * big. + */ + // To calculate: 5^exp + BigInteger powerOfFive = bigFivePows[1].pow(Integer.MAX_VALUE); + res = powerOfFive; + long longExp = exp - Integer.MAX_VALUE; + + intExp = (int) (exp % Integer.MAX_VALUE); + while (longExp > Integer.MAX_VALUE) { + res = res.multiply(powerOfFive); + longExp -= Integer.MAX_VALUE; + } + res = res.multiply(bigFivePows[1].pow(intExp)); + // To calculate: 5^exp << exp + res = res.shiftLeft(Integer.MAX_VALUE); + longExp = exp - Integer.MAX_VALUE; + while (longExp > Integer.MAX_VALUE) { + res = res.shiftLeft(Integer.MAX_VALUE); + longExp -= Integer.MAX_VALUE; + } + res = res.shiftLeft(intExp); + } + } catch (OutOfMemoryError error) { + throw new ArithmeticException(error.getMessage()); } - res = res.shiftLeft(intExp); + return res; } diff --git a/luni/src/main/java/java/net/AddressCache.java b/luni/src/main/java/java/net/AddressCache.java index 194761a..2aba78b 100644 --- a/luni/src/main/java/java/net/AddressCache.java +++ b/luni/src/main/java/java/net/AddressCache.java @@ -37,8 +37,36 @@ class AddressCache { private static final long TTL_NANOS = 2 * 1000000000L; // The actual cache. - private final BasicLruCache<String, AddressCacheEntry> cache - = new BasicLruCache<String, AddressCacheEntry>(MAX_ENTRIES); + private final BasicLruCache<AddressCacheKey, AddressCacheEntry> cache + = new BasicLruCache<AddressCacheKey, AddressCacheEntry>(MAX_ENTRIES); + + static class AddressCacheKey { + private final String mHostname; + private final int mNetId; + + AddressCacheKey(String hostname, int netId) { + mHostname = hostname; + mNetId = netId; + } + + @Override public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AddressCacheKey)) { + return false; + } + AddressCacheKey lhs = (AddressCacheKey) o; + return mHostname.equals(lhs.mHostname) && mNetId == lhs.mNetId; + } + + @Override public int hashCode() { + int result = 17; + result = 31 * result + mNetId; + result = 31 * result + mHostname.hashCode(); + return result; + } + } static class AddressCacheEntry { // Either an InetAddress[] for a positive entry, @@ -67,12 +95,12 @@ class AddressCache { } /** - * Returns the cached InetAddress[] associated with 'hostname'. Returns null if nothing is known - * about 'hostname'. Returns a String suitable for use as an UnknownHostException detail - * message if 'hostname' is known not to exist. + * Returns the cached InetAddress[] for 'hostname' on network 'netId'. Returns null + * if nothing is known about 'hostname'. Returns a String suitable for use as an + * UnknownHostException detail message if 'hostname' is known not to exist. */ - public Object get(String hostname) { - AddressCacheEntry entry = cache.get(hostname); + public Object get(String hostname, int netId) { + AddressCacheEntry entry = cache.get(new AddressCacheKey(hostname, netId)); // Do we have a valid cache entry? if (entry != null && entry.expiryNanos >= System.nanoTime()) { return entry.value; @@ -86,15 +114,15 @@ class AddressCache { * Associates the given 'addresses' with 'hostname'. The association will expire after a * certain length of time. */ - public void put(String hostname, InetAddress[] addresses) { - cache.put(hostname, new AddressCacheEntry(addresses)); + public void put(String hostname, int netId, InetAddress[] addresses) { + cache.put(new AddressCacheKey(hostname, netId), new AddressCacheEntry(addresses)); } /** * Records that 'hostname' is known not to have any associated addresses. (I.e. insert a * negative cache entry.) */ - public void putUnknownHost(String hostname, String detailMessage) { - cache.put(hostname, new AddressCacheEntry(detailMessage)); + public void putUnknownHost(String hostname, int netId, String detailMessage) { + cache.put(new AddressCacheKey(hostname, netId), new AddressCacheEntry(detailMessage)); } } diff --git a/luni/src/main/java/java/net/DatagramSocket.java b/luni/src/main/java/java/net/DatagramSocket.java index 49f141a..f9b72d8 100644 --- a/luni/src/main/java/java/net/DatagramSocket.java +++ b/luni/src/main/java/java/net/DatagramSocket.java @@ -17,14 +17,14 @@ package java.net; +import android.system.ErrnoException; import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.nio.channels.DatagramChannel; -import libcore.io.ErrnoException; import libcore.io.IoBridge; import libcore.io.Libcore; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * This class implements a UDP socket for sending and receiving {@code @@ -114,6 +114,17 @@ public class DatagramSocket implements Closeable { } /** + * Sets the DatagramSocket and its related DatagramSocketImpl state as if a successful close() + * took place, without actually performing an OS close(). + * + * @hide used in java.nio + */ + public void onClose() { + isClosed = true; + impl.onClose(); + } + + /** * Disconnects this UDP datagram socket from the remote host. This method * called on an unconnected socket does nothing. */ @@ -127,6 +138,19 @@ public class DatagramSocket implements Closeable { isConnected = false; } + /** + * Sets the DatagramSocket and its related DatagramSocketImpl state as if a successful + * disconnect() took place, without actually performing a disconnect(). + * + * @hide used in java.nio + */ + public void onDisconnect() { + address = null; + port = -1; + isConnected = false; + impl.onDisconnect(); + } + synchronized void createSocket(int aPort, InetAddress addr) throws SocketException { impl = factory != null ? factory.createDatagramSocketImpl() : new PlainDatagramSocketImpl(); @@ -152,8 +176,8 @@ public class DatagramSocket implements Closeable { } /** - * Returns the local address to which this socket is bound, - * or {@code null} if this socket is closed. + * Returns the local address to which this socket is bound, a wildcard address if this + * socket is not yet bound, or {@code null} if this socket is closed. */ public InetAddress getLocalAddress() { try { @@ -439,9 +463,12 @@ public class DatagramSocket implements Closeable { */ public void bind(SocketAddress localAddr) throws SocketException { checkOpen(); - int localPort = 0; - InetAddress addr = Inet4Address.ANY; - if (localAddr != null) { + int localPort; + InetAddress addr; + if (localAddr == null) { + localPort = 0; + addr = Inet4Address.ANY; + } else { if (!(localAddr instanceof InetSocketAddress)) { throw new IllegalArgumentException("Local address not an InetSocketAddress: " + localAddr.getClass()); @@ -459,6 +486,17 @@ public class DatagramSocket implements Closeable { } /** + * Sets the DatagramSocket and its related DatagramSocketImpl state as if a successful bind() + * took place, without actually performing an OS bind(). + * + * @hide used in java.nio + */ + public void onBind(InetAddress localAddress, int localPort) { + isBound = true; + impl.onBind(localAddress, localPort); + } + + /** * Connects this datagram socket to the address and port specified by {@code peer}. * Future calls to {@link #send} will use this as the default target, and {@link #receive} * will only accept packets from this source. @@ -492,6 +530,19 @@ public class DatagramSocket implements Closeable { } /** + * Sets the DatagramSocket and its related DatagramSocketImpl state as if a successful connect() + * took place, without actually performing an OS connect(). + * + * @hide used in java.nio + */ + public void onConnect(InetAddress remoteAddress, int remotePort) { + isConnected = true; + this.address = remoteAddress; + this.port = remotePort; + impl.onConnect(remoteAddress, remotePort); + } + + /** * Connects this datagram socket to the specific {@code address} and {@code port}. * Future calls to {@link #send} will use this as the default target, and {@link #receive} * will only accept packets from this source. @@ -537,10 +588,11 @@ public class DatagramSocket implements Closeable { } /** - * Returns the {@code SocketAddress} this socket is bound to, or null for an unbound socket. + * Returns the {@code SocketAddress} this socket is bound to, or {@code null} for an unbound or + * closed socket. */ public SocketAddress getLocalSocketAddress() { - if (!isBound()) { + if (isClosed() || !isBound()) { return null; } return new InetSocketAddress(getLocalAddress(), getLocalPort()); diff --git a/luni/src/main/java/java/net/DatagramSocketImpl.java b/luni/src/main/java/java/net/DatagramSocketImpl.java index 097eb17..1a39987 100644 --- a/luni/src/main/java/java/net/DatagramSocketImpl.java +++ b/luni/src/main/java/java/net/DatagramSocketImpl.java @@ -268,4 +268,40 @@ public abstract class DatagramSocketImpl implements SocketOptions { * if an error occurs while peeking at the data. */ protected abstract int peekData(DatagramPacket pack) throws IOException; + + /** + * Initialize the bind() state. + * @hide used in java.nio. + */ + protected void onBind(InetAddress localAddress, int localPort) { + // Do not add any code to these methods. They are concrete only to preserve API + // compatibility. + } + + /** + * Initialize the connect() state. + * @hide used in java.nio. + */ + protected void onConnect(InetAddress remoteAddress, int remotePort) { + // Do not add any code to these methods. They are concrete only to preserve API + // compatibility. + } + + /** + * Initialize the disconnected state. + * @hide used in java.nio. + */ + protected void onDisconnect() { + // Do not add any code to these methods. They are concrete only to preserve API + // compatibility. + } + + /** + * Initialize the closed state. + * @hide used in java.nio. + */ + protected void onClose() { + // Do not add any code to these methods. They are concrete only to preserve API + // compatibility. + } } diff --git a/luni/src/main/java/java/net/HttpCookie.java b/luni/src/main/java/java/net/HttpCookie.java index ce1a8d2..dd81fd6 100644 --- a/luni/src/main/java/java/net/HttpCookie.java +++ b/luni/src/main/java/java/net/HttpCookie.java @@ -53,10 +53,12 @@ import libcore.util.Objects; * in this format is {@code 1}. * </ul> * - * <p>This implementation silently discards unrecognized attributes. In - * particular, the {@code HttpOnly} attribute is widely served but isn't in any - * of the above specs. It was introduced by Internet Explorer to prevent server - * cookies from being exposed in the DOM to JavaScript, etc. + * <p>Support for the "HttpOnly" attribute specified in + * <a href="http://tools.ietf.org/html/rfc6265">RFC 6265</a> is also included. RFC 6265 is intended + * to obsolete RFC 2965. Support for features from RFC 2965 that have been deprecated by RFC 6265 + * such as Cookie2, Set-Cookie2 headers and version information remain supported by this class. + * + * <p>This implementation silently discards unrecognized attributes. * * @since 1.6 */ @@ -65,16 +67,17 @@ public final class HttpCookie implements Cloneable { private static final Set<String> RESERVED_NAMES = new HashSet<String>(); static { - RESERVED_NAMES.add("comment"); // RFC 2109 RFC 2965 - RESERVED_NAMES.add("commenturl"); // RFC 2965 - RESERVED_NAMES.add("discard"); // RFC 2965 - RESERVED_NAMES.add("domain"); // Netscape RFC 2109 RFC 2965 + RESERVED_NAMES.add("comment"); // RFC 2109 RFC 2965 RFC 6265 + RESERVED_NAMES.add("commenturl"); // RFC 2965 RFC 6265 + RESERVED_NAMES.add("discard"); // RFC 2965 RFC 6265 + RESERVED_NAMES.add("domain"); // Netscape RFC 2109 RFC 2965 RFC 6265 RESERVED_NAMES.add("expires"); // Netscape - RESERVED_NAMES.add("max-age"); // RFC 2109 RFC 2965 - RESERVED_NAMES.add("path"); // Netscape RFC 2109 RFC 2965 - RESERVED_NAMES.add("port"); // RFC 2965 - RESERVED_NAMES.add("secure"); // Netscape RFC 2109 RFC 2965 - RESERVED_NAMES.add("version"); // RFC 2109 RFC 2965 + RESERVED_NAMES.add("httponly"); // RFC 6265 + RESERVED_NAMES.add("max-age"); // RFC 2109 RFC 2965 RFC 6265 + RESERVED_NAMES.add("path"); // Netscape RFC 2109 RFC 2965 RFC 6265 + RESERVED_NAMES.add("port"); // RFC 2965 RFC 6265 + RESERVED_NAMES.add("secure"); // Netscape RFC 2109 RFC 2965 RFC 6265 + RESERVED_NAMES.add("version"); // RFC 2109 RFC 2965 RFC 6265 } /** @@ -332,14 +335,26 @@ public final class HttpCookie implements Cloneable { } } } else if (name.equals("max-age") && cookie.maxAge == -1L) { - hasMaxAge = true; - cookie.maxAge = Long.parseLong(value); + // RFCs 2109 and 2965 suggests a zero max-age as a way of deleting a cookie. + // RFC 6265 specifies the value must be > 0 but also describes what to do if the + // value is negative, zero or non-numeric in section 5.2.2. The RI does none of this + // and accepts negative, positive values and throws an IllegalArgumentException + // if the value is non-numeric. + try { + long maxAge = Long.parseLong(value); + hasMaxAge = true; + cookie.maxAge = maxAge; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid max-age: " + value); + } } else if (name.equals("path") && cookie.path == null) { cookie.path = value; } else if (name.equals("port") && cookie.portList == null) { cookie.portList = value != null ? value : ""; } else if (name.equals("secure")) { cookie.secure = true; + } else if (name.equals("httponly")) { + cookie.httpOnly = true; } else if (name.equals("version") && !hasVersion) { cookie.version = Integer.parseInt(value); } @@ -430,6 +445,7 @@ public final class HttpCookie implements Cloneable { private String path; private String portList; private boolean secure; + private boolean httpOnly; private String value; private int version = 1; @@ -698,7 +714,21 @@ public final class HttpCookie implements Cloneable { /** * Returns a string representing this cookie in the format used by the - * {@code Cookie} header line in an HTTP request. + * {@code Cookie} header line in an HTTP request as specified by RFC 2965 section 3.3.4. + * + * <p>The resulting string does not include a "Cookie:" prefix or any version information. + * The returned {@code String} is not suitable for passing to {@link #parse(String)}: Several of + * the attributes that would be needed to preserve all of the cookie's information are omitted. + * The String is formatted for an HTTP request not an HTTP response. + * + * <p>The attributes included and the format depends on the cookie's {@code version}: + * <ul> + * <li>Version 0: Includes only the name and value. Conforms to RFC 2965 (for + * version 0 cookies). This should also be used to conform with RFC 6265. + * </li> + * <li>Version 1: Includes the name and value, and Path, Domain and Port attributes. + * Conforms to RFC 2965 (for version 1 cookies).</li> + * </ul> */ @Override public String toString() { if (version == 0) { diff --git a/luni/src/main/java/java/net/HttpURLConnection.java b/luni/src/main/java/java/net/HttpURLConnection.java index 89a4bc4..4e5b4ee 100644 --- a/luni/src/main/java/java/net/HttpURLConnection.java +++ b/luni/src/main/java/java/net/HttpURLConnection.java @@ -784,11 +784,15 @@ public abstract class HttpURLConnection extends URLConnection { * only servers may not support this mode. * * <p>When HTTP chunked encoding is used, the stream is divided into - * chunks, each prefixed with a header containing the chunk's size. Setting - * a large chunk length requires a large internal buffer, potentially - * wasting memory. Setting a small chunk length increases the number of + * chunks, each prefixed with a header containing the chunk's size. + * A large chunk length requires a large internal buffer, potentially + * wasting memory. A small chunk length increases the number of * bytes that must be transmitted because of the header on every chunk. - * Most caller should use {@code 0} to get the system default. + * + * <p>Implementation details: In some releases the {@code chunkLength} is + * treated as a hint: chunks sent to the server may actually be larger or + * smaller. To force a chunk to be sent to the server call + * {@link java.io.OutputStream#flush()}. * * @see #setFixedLengthStreamingMode * @param chunkLength the length to use, or {@code 0} for the default chunk diff --git a/luni/src/main/java/java/net/Inet4Address.java b/luni/src/main/java/java/net/Inet4Address.java index 7c26639..f0b1b5b 100644 --- a/luni/src/main/java/java/net/Inet4Address.java +++ b/luni/src/main/java/java/net/Inet4Address.java @@ -20,7 +20,7 @@ package java.net; import java.io.ObjectStreamException; import java.nio.ByteOrder; import libcore.io.Memory; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * An IPv4 address. See {@link InetAddress}. diff --git a/luni/src/main/java/java/net/Inet6Address.java b/luni/src/main/java/java/net/Inet6Address.java index 37e9c18..8ab0f8d 100644 --- a/luni/src/main/java/java/net/Inet6Address.java +++ b/luni/src/main/java/java/net/Inet6Address.java @@ -23,7 +23,7 @@ import java.io.ObjectOutputStream; import java.io.ObjectStreamField; import java.util.Arrays; import java.util.Enumeration; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * An IPv6 address. See {@link InetAddress}. diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java index 98ad098..5cfa15a 100644 --- a/luni/src/main/java/java/net/InetAddress.java +++ b/luni/src/main/java/java/net/InetAddress.java @@ -17,6 +17,9 @@ package java.net; +import android.system.ErrnoException; +import android.system.GaiException; +import android.system.StructAddrinfo; import dalvik.system.BlockGuard; import java.io.FileDescriptor; import java.io.IOException; @@ -28,18 +31,13 @@ import java.io.Serializable; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.List; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -import libcore.io.ErrnoException; -import libcore.io.GaiException; +import java.util.concurrent.CountDownLatch; +import java.util.List; import libcore.io.IoBridge; import libcore.io.Libcore; import libcore.io.Memory; -import libcore.io.StructAddrinfo; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * An Internet Protocol (IP) address. This can be either an IPv4 address or an IPv6 address, and @@ -129,6 +127,9 @@ public class InetAddress implements Serializable { private static final long serialVersionUID = 3286316764910316507L; + /** Using NetID of NETID_UNSET indicates resolution should be done on default network. */ + private static final int NETID_UNSET = 0; + private int family; byte[] ipaddress; @@ -211,14 +212,29 @@ public class InetAddress implements Serializable { * @throws UnknownHostException if the address lookup fails. */ public static InetAddress[] getAllByName(String host) throws UnknownHostException { - return getAllByNameImpl(host).clone(); + return getAllByNameImpl(host, NETID_UNSET).clone(); } /** - * Returns the InetAddresses for {@code host}. The returned array is shared - * and must be cloned before it is returned to application code. + * Operates identically to {@code getAllByName} except host resolution is + * performed on the network designated by {@code netId}. + * + * @param host the hostname or literal IP string to be resolved. + * @param netId the network to use for host resolution. + * @return the array of addresses associated with the specified host. + * @throws UnknownHostException if the address lookup fails. + * @hide internal use only */ - private static InetAddress[] getAllByNameImpl(String host) throws UnknownHostException { + public static InetAddress[] getAllByNameOnNet(String host, int netId) throws UnknownHostException { + return getAllByNameImpl(host, netId).clone(); + } + + /** + * Returns the InetAddresses for {@code host} on network {@code netId}. The + * returned array is shared and must be cloned before it is returned to + * application code. + */ + private static InetAddress[] getAllByNameImpl(String host, int netId) throws UnknownHostException { if (host == null || host.isEmpty()) { return loopbackAddresses(); } @@ -233,7 +249,7 @@ public class InetAddress implements Serializable { return new InetAddress[] { result }; } - return lookupHostByName(host).clone(); + return lookupHostByName(host, netId).clone(); } private static InetAddress makeInetAddress(byte[] bytes, String hostName) throws UnknownHostException { @@ -266,7 +282,7 @@ public class InetAddress implements Serializable { hints.ai_flags = AI_NUMERICHOST; InetAddress[] addresses = null; try { - addresses = Libcore.os.getaddrinfo(address, hints); + addresses = Libcore.os.android_getaddrinfo(address, hints, NETID_UNSET); } catch (GaiException ignored) { } return (addresses != null) ? addresses[0] : null; @@ -286,7 +302,22 @@ public class InetAddress implements Serializable { * if the address lookup fails. */ public static InetAddress getByName(String host) throws UnknownHostException { - return getAllByNameImpl(host)[0]; + return getAllByNameImpl(host, NETID_UNSET)[0]; + } + + /** + * Operates identically to {@code getByName} except host resolution is + * performed on the network designated by {@code netId}. + * + * @param host + * the hostName to be resolved to an address or {@code null}. + * @param netId the network to use for host resolution. + * @return the {@code InetAddress} instance representing the host. + * @throws UnknownHostException if the address lookup fails. + * @hide internal use only + */ + public static InetAddress getByNameOnNet(String host, int netId) throws UnknownHostException { + return getAllByNameImpl(host, netId)[0]; } /** @@ -362,7 +393,7 @@ public class InetAddress implements Serializable { */ public static InetAddress getLocalHost() throws UnknownHostException { String host = Libcore.os.uname().nodename; - return lookupHostByName(host)[0]; + return lookupHostByName(host, NETID_UNSET)[0]; } /** @@ -379,12 +410,14 @@ public class InetAddress implements Serializable { * Resolves a hostname to its IP addresses using a cache. * * @param host the hostname to resolve. + * @param netId the network to perform resolution upon. * @return the IP addresses of the host. */ - private static InetAddress[] lookupHostByName(String host) throws UnknownHostException { + private static InetAddress[] lookupHostByName(String host, int netId) + throws UnknownHostException { BlockGuard.getThreadPolicy().onNetwork(); // Do we have a result cached? - Object cachedResult = addressCache.get(host); + Object cachedResult = addressCache.get(host, netId); if (cachedResult != null) { if (cachedResult instanceof InetAddress[]) { // A cached positive result. @@ -402,12 +435,12 @@ public class InetAddress implements Serializable { // for SOCK_STREAM and one for SOCK_DGRAM. Since we do not return the family // anyway, just pick one. hints.ai_socktype = SOCK_STREAM; - InetAddress[] addresses = Libcore.os.getaddrinfo(host, hints); + InetAddress[] addresses = Libcore.os.android_getaddrinfo(host, hints, netId); // TODO: should getaddrinfo set the hostname of the InetAddresses it returns? for (InetAddress address : addresses) { address.hostName = host; } - addressCache.put(host, addresses); + addressCache.put(host, netId, addresses); return addresses; } catch (GaiException gaiException) { // If the failure appears to have been a lack of INTERNET permission, throw a clear @@ -420,7 +453,7 @@ public class InetAddress implements Serializable { } // Otherwise, throw an UnknownHostException. String detailMessage = "Unable to resolve host \"" + host + "\": " + Libcore.os.gai_strerror(gaiException.error); - addressCache.putUnknownHost(host, detailMessage); + addressCache.putUnknownHost(host, netId, detailMessage); throw gaiException.rethrowAsUnknownHostException(detailMessage); } } @@ -734,7 +767,7 @@ public class InetAddress implements Serializable { } } - IoBridge.closeSocket(fd); + IoBridge.closeAndSignalBlockedThreads(fd); return reached; } diff --git a/luni/src/main/java/java/net/InetUnixAddress.java b/luni/src/main/java/java/net/InetUnixAddress.java index 44b9cba..51236e2 100644 --- a/luni/src/main/java/java/net/InetUnixAddress.java +++ b/luni/src/main/java/java/net/InetUnixAddress.java @@ -18,7 +18,7 @@ package java.net; import java.nio.charset.StandardCharsets; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * An AF_UNIX address. See {@link InetAddress}. diff --git a/luni/src/main/java/java/net/JarURLConnection.java b/luni/src/main/java/java/net/JarURLConnection.java index 4b84893..e5c8fac 100644 --- a/luni/src/main/java/java/net/JarURLConnection.java +++ b/luni/src/main/java/java/net/JarURLConnection.java @@ -18,11 +18,13 @@ package java.net; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.cert.Certificate; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; +import libcore.net.UriCodec; /** * This class establishes a connection to a {@code jar:} URL using the {@code @@ -64,12 +66,13 @@ public abstract class JarURLConnection extends URLConnection { */ protected JarURLConnection(URL url) throws MalformedURLException { super(url); - file = url.getFile(); + file = decode(url.getFile()); + int sepIdx; if ((sepIdx = file.indexOf("!/")) < 0) { throw new MalformedURLException(); } - fileURL = new URL(url.getFile().substring(0,sepIdx)); + fileURL = new URL(file.substring(0, sepIdx)); sepIdx += 2; if (file.length() == sepIdx) { return; @@ -189,4 +192,17 @@ public abstract class JarURLConnection extends URLConnection { Manifest m = getJarFile().getManifest(); return (m == null) ? null : m.getMainAttributes(); } + + private static String decode(String encoded) throws MalformedURLException { + try { + // "+" means "+" in URLs. i.e. like RFC 3986, not like + // MIME application/x-www-form-urlencoded + final boolean convertPlus = false; + return UriCodec.decode( + encoded, convertPlus, StandardCharsets.UTF_8, true /* throwOnFailure */); + } catch (IllegalArgumentException e) { + throw new MalformedURLException("Unable to decode URL", e); + } + } + } diff --git a/luni/src/main/java/java/net/MulticastSocket.java b/luni/src/main/java/java/net/MulticastSocket.java index 6f4a582..24e66c5 100644 --- a/luni/src/main/java/java/net/MulticastSocket.java +++ b/luni/src/main/java/java/net/MulticastSocket.java @@ -229,6 +229,9 @@ public class MulticastSocket extends DatagramSocket { private void checkJoinOrLeave(InetAddress groupAddr) throws IOException { checkOpen(); + if (groupAddr == null) { + throw new IllegalArgumentException("groupAddress == null"); + } if (!groupAddr.isMulticastAddress()) { throw new IOException("Not a multicast group: " + groupAddr); } @@ -351,7 +354,8 @@ public class MulticastSocket extends DatagramSocket { /** * Disables multicast loopback if {@code disable == true}. * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the - * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}. + * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}: true means disabled, false + * means enabled. * * @throws SocketException if an error occurs. */ diff --git a/luni/src/main/java/java/net/NetworkInterface.java b/luni/src/main/java/java/net/NetworkInterface.java index 3128b98..852c09b 100644 --- a/luni/src/main/java/java/net/NetworkInterface.java +++ b/luni/src/main/java/java/net/NetworkInterface.java @@ -17,6 +17,7 @@ package java.net; +import android.system.ErrnoException; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; @@ -26,10 +27,9 @@ import java.util.Collections; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; -import libcore.io.ErrnoException; import libcore.io.IoUtils; import libcore.io.Libcore; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * This class is used to represent a network interface of the local device. An diff --git a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java index 3226527..eb0c99d 100644 --- a/luni/src/main/java/java/net/PlainDatagramSocketImpl.java +++ b/luni/src/main/java/java/net/PlainDatagramSocketImpl.java @@ -17,23 +17,15 @@ package java.net; +import android.system.ErrnoException; +import android.system.StructGroupReq; import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocketImpl; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.NetworkInterface; -import java.net.SocketAddress; -import java.net.SocketException; -import java.net.UnknownHostException; -import libcore.io.ErrnoException; import libcore.io.IoBridge; import libcore.io.Libcore; -import libcore.io.StructGroupReq; import libcore.util.EmptyArray; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * @hide used in java.nio. @@ -78,15 +70,25 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { } @Override + protected void onBind(InetAddress localAddress, int localPort) { + this.localPort = localPort; + } + + @Override public synchronized void close() { guard.close(); try { - IoBridge.closeSocket(fd); + IoBridge.closeAndSignalBlockedThreads(fd); } catch (IOException ignored) { } } @Override + protected void onClose() { + guard.close(); + } + + @Override public void create() throws SocketException { this.fd = IoBridge.socket(false); } @@ -179,7 +181,8 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { public void send(DatagramPacket packet) throws IOException { int port = isNativeConnected ? 0 : packet.getPort(); InetAddress address = isNativeConnected ? null : packet.getAddress(); - IoBridge.sendto(fd, packet.getData(), packet.getOffset(), packet.getLength(), 0, address, port); + IoBridge.sendto(fd, packet.getData(), packet.getOffset(), packet.getLength(), 0, address, + port); } public void setOption(int option, Object value) throws SocketException { @@ -211,6 +214,13 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { } @Override + protected void onConnect(InetAddress remoteAddress, int remotePort) { + isNativeConnected = true; + connectedAddress = remoteAddress; + connectedPort = remotePort; + } + + @Override public void disconnect() { try { Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0); @@ -224,6 +234,13 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { isNativeConnected = false; } + @Override + protected void onDisconnect() { + connectedPort = -1; + connectedAddress = null; + isNativeConnected = false; + } + /** * Set the received address and port in the packet. We do this when the * Datagram socket is connected at the native level and the diff --git a/luni/src/main/java/java/net/PlainSocketImpl.java b/luni/src/main/java/java/net/PlainSocketImpl.java index 18942d6..4e5ba44 100644 --- a/luni/src/main/java/java/net/PlainSocketImpl.java +++ b/luni/src/main/java/java/net/PlainSocketImpl.java @@ -17,28 +17,19 @@ package java.net; +import android.system.ErrnoException; import dalvik.system.CloseGuard; import java.io.FileDescriptor; -import java.io.IOException; import java.io.InputStream; +import java.io.IOException; import java.io.OutputStream; -import java.net.ConnectException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.SocketAddress; -import java.net.SocketException; -import java.net.SocketImpl; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; import java.nio.ByteOrder; import java.util.Arrays; -import libcore.io.ErrnoException; import libcore.io.IoBridge; import libcore.io.Libcore; import libcore.io.Memory; import libcore.io.Streams; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * @hide used in java.nio. @@ -120,15 +111,6 @@ public class PlainSocketImpl extends SocketImpl { return proxy != null && proxy.type() == Proxy.Type.SOCKS; } - public void initLocalPort(int localPort) { - this.localport = localPort; - } - - public void initRemoteAddressAndPort(InetAddress remoteAddress, int remotePort) { - this.address = remoteAddress; - this.port = remotePort; - } - private void checkNotClosed() throws IOException { if (!fd.valid()) { throw new SocketException("Socket is closed"); @@ -148,7 +130,6 @@ public class PlainSocketImpl extends SocketImpl { @Override protected void bind(InetAddress address, int port) throws IOException { IoBridge.bind(fd, address, port); - this.address = address; if (port != 0) { this.localport = port; } else { @@ -157,9 +138,19 @@ public class PlainSocketImpl extends SocketImpl { } @Override + public void onBind(InetAddress localAddress, int localPort) { + localport = localPort; + } + + @Override protected synchronized void close() throws IOException { guard.close(); - IoBridge.closeSocket(fd); + IoBridge.closeAndSignalBlockedThreads(fd); + } + + @Override + public void onClose() { + guard.close(); } @Override @@ -191,8 +182,14 @@ public class PlainSocketImpl extends SocketImpl { } else { IoBridge.connect(fd, normalAddr, aPort, timeout); } - super.address = normalAddr; - super.port = aPort; + address = normalAddr; + port = aPort; + } + + @Override + public void onConnect(InetAddress remoteAddress, int remotePort) { + address = remoteAddress; + port = remotePort; } @Override diff --git a/luni/src/main/java/java/net/ServerSocket.java b/luni/src/main/java/java/net/ServerSocket.java index 399511f..72b197f 100644 --- a/luni/src/main/java/java/net/ServerSocket.java +++ b/luni/src/main/java/java/net/ServerSocket.java @@ -21,6 +21,8 @@ import java.io.Closeable; import java.io.IOException; import java.nio.channels.ServerSocketChannel; +import libcore.io.IoBridge; + /** * This class represents a server-side socket that waits for incoming client * connections. A {@code ServerSocket} handles the requests and sends back an @@ -49,6 +51,8 @@ public class ServerSocket implements Closeable { private boolean isClosed; + private InetAddress localAddress; + /** * Constructs a new unbound {@code ServerSocket}. * @@ -99,7 +103,7 @@ public class ServerSocket implements Closeable { impl.create(true); try { impl.bind(addr, port); - isBound = true; + readBackBindState(); impl.listen(backlog > 0 ? backlog : DEFAULT_BACKLOG); } catch (IOException e) { close(); @@ -109,6 +113,14 @@ public class ServerSocket implements Closeable { } /** + * Read the cached isBound and localAddress state from the underlying OS socket. + */ + private void readBackBindState() throws SocketException { + localAddress = IoBridge.getSocketLocalAddress(impl.fd); + isBound = true; + } + + /** * Waits for an incoming request and blocks until the connection is opened. * This method returns a socket object representing the just opened * connection. @@ -152,8 +164,8 @@ public class ServerSocket implements Closeable { } /** - * Gets the local IP address of this server socket or {@code null} if the - * socket is unbound. This is useful for multihomed hosts. + * Gets the local IP address of this server socket if this socket has ever been bound, + * {@code null} otherwise. This is useful for multihomed hosts. * * @return the local address of this server socket. */ @@ -161,12 +173,13 @@ public class ServerSocket implements Closeable { if (!isBound()) { return null; } - return impl.getInetAddress(); + return localAddress; } /** - * Gets the local port of this server socket or {@code -1} if the socket is - * unbound. + * Gets the local port of this server socket or {@code -1} if the socket is not bound. + * If the socket has ever been bound this method will return the local port it was bound to, + * even after it has been closed. * * @return the local port this server is listening on. */ @@ -300,9 +313,12 @@ public class ServerSocket implements Closeable { if (isBound()) { throw new BindException("Socket is already bound"); } - int port = 0; - InetAddress addr = Inet4Address.ANY; - if (localAddr != null) { + InetAddress addr; + int port; + if (localAddr == null) { + addr = Inet4Address.ANY; + port = 0; + } else { if (!(localAddr instanceof InetSocketAddress)) { throw new IllegalArgumentException("Local address not an InetSocketAddress: " + localAddr.getClass()); @@ -317,7 +333,7 @@ public class ServerSocket implements Closeable { synchronized (this) { try { impl.bind(addr, port); - isBound = true; + readBackBindState(); impl.listen(backlog > 0 ? backlog : DEFAULT_BACKLOG); } catch (IOException e) { close(); @@ -327,8 +343,9 @@ public class ServerSocket implements Closeable { } /** - * Gets the local socket address of this server socket or {@code null} if - * the socket is unbound. This is useful on multihomed hosts. + * Gets the local socket address of this server socket or {@code null} if the socket is unbound. + * This is useful on multihomed hosts. If the socket has ever been bound this method will return + * the local address it was bound to, even after it has been closed. * * @return the local socket address and port this socket is bound to. */ @@ -336,7 +353,7 @@ public class ServerSocket implements Closeable { if (!isBound()) { return null; } - return new InetSocketAddress(getInetAddress(), getLocalPort()); + return new InetSocketAddress(localAddress, getLocalPort()); } /** diff --git a/luni/src/main/java/java/net/Socket.java b/luni/src/main/java/java/net/Socket.java index 36fdf28..5dd350a 100644 --- a/luni/src/main/java/java/net/Socket.java +++ b/luni/src/main/java/java/net/Socket.java @@ -312,12 +312,29 @@ public class Socket implements Closeable { */ public synchronized void close() throws IOException { isClosed = true; - // RI compatibility: the RI returns the any address (but the original local port) after close. + isConnected = false; + // RI compatibility: the RI returns the any address (but the original local port) after + // close. localAddress = Inet4Address.ANY; impl.close(); } /** + * Sets the Socket and its related SocketImpl state as if a successful close() took place, + * without actually performing an OS close(). + * + * @hide used in java.nio + */ + public void onClose() { + isClosed = true; + isConnected = false; + // RI compatibility: the RI returns the any address (but the original local port) after + // close. + localAddress = Inet4Address.ANY; + impl.onClose(); + } + + /** * Returns the IP address of the target host this socket is connected to, or null if this * socket is not yet connected. */ @@ -329,7 +346,9 @@ public class Socket implements Closeable { } /** - * Returns an input stream to read data from this socket. + * Returns an input stream to read data from this socket. If the socket has an associated + * {@link SocketChannel} and that channel is in non-blocking mode then reads from the + * stream will throw a {@link java.nio.channels.IllegalBlockingModeException}. * * @return the byte-oriented input stream. * @throws IOException @@ -353,15 +372,16 @@ public class Socket implements Closeable { } /** - * Returns the local IP address this socket is bound to, or {@code InetAddress.ANY} if - * the socket is unbound. + * Returns the local IP address this socket is bound to, or an address for which + * {@link InetAddress#isAnyLocalAddress()} returns true if the socket is closed or unbound. */ public InetAddress getLocalAddress() { return localAddress; } /** - * Returns the local port this socket is bound to, or -1 if the socket is unbound. + * Returns the local port this socket is bound to, or -1 if the socket is unbound. If the socket + * has been closed this method will still return the local port the socket was bound to. */ public int getLocalPort() { if (!isBound()) { @@ -371,7 +391,9 @@ public class Socket implements Closeable { } /** - * Returns an output stream to write data into this socket. + * Returns an output stream to write data into this socket. If the socket has an associated + * {@link SocketChannel} and that channel is in non-blocking mode then writes to the + * stream will throw a {@link java.nio.channels.IllegalBlockingModeException}. * * @return the byte-oriented output stream. * @throws IOException @@ -564,6 +586,7 @@ public class Socket implements Closeable { impl.bind(addr, localPort); } isBound = true; + cacheLocalAddress(); impl.connect(dstAddress, dstPort); isConnected = true; cacheLocalAddress(); @@ -672,9 +695,10 @@ public class Socket implements Closeable { } /** - * Returns the local address and port of this socket as a SocketAddress or - * null if the socket is unbound. This is useful on multihomed - * hosts. + * Returns the local address and port of this socket as a SocketAddress or null if the socket + * has never been bound. If the socket is closed but has previously been bound then an address + * for which {@link InetAddress#isAnyLocalAddress()} returns true will be returned with the + * previously-bound port. This is useful on multihomed hosts. */ public SocketAddress getLocalSocketAddress() { if (!isBound()) { @@ -744,9 +768,12 @@ public class Socket implements Closeable { throw new BindException("Socket is already bound"); } - int port = 0; - InetAddress addr = Inet4Address.ANY; - if (localAddr != null) { + int port; + InetAddress addr; + if (localAddr == null) { + port = 0; + addr = Inet4Address.ANY; + } else { if (!(localAddr instanceof InetSocketAddress)) { throw new IllegalArgumentException("Local address not an InetSocketAddress: " + localAddr.getClass()); @@ -771,6 +798,18 @@ public class Socket implements Closeable { } /** + * Sets the Socket and its related SocketImpl state as if a successful bind() took place, + * without actually performing an OS bind(). + * + * @hide used in java.nio + */ + public void onBind(InetAddress localAddress, int localPort) { + isBound = true; + this.localAddress = localAddress; + impl.onBind(localAddress, localPort); + } + + /** * Connects this socket to the given remote host address and port specified * by the SocketAddress {@code remoteAddr}. * @@ -851,6 +890,17 @@ public class Socket implements Closeable { } /** + * Sets the Socket and its related SocketImpl state as if a successful connect() took place, + * without actually performing an OS connect(). + * + * @hide internal use only + */ + public void onConnect(InetAddress remoteAddress, int remotePort) { + isConnected = true; + impl.onConnect(remoteAddress, remotePort); + } + + /** * Returns whether the incoming channel of the socket has already been * closed. * diff --git a/luni/src/main/java/java/net/SocketImpl.java b/luni/src/main/java/java/net/SocketImpl.java index 92de9cf..bd36ec7 100644 --- a/luni/src/main/java/java/net/SocketImpl.java +++ b/luni/src/main/java/java/net/SocketImpl.java @@ -294,4 +294,31 @@ public abstract class SocketImpl implements SocketOptions { */ protected void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { } + + /** + * Initialize the bind() state. + * @hide used in java.nio. + */ + public void onBind(InetAddress localAddress, int localPort) { + // Do not add any code to these methods. They are concrete only to preserve API + // compatibility. + } + + /** + * Initialize the connect() state. + * @hide used in java.nio. + */ + public void onConnect(InetAddress remoteAddress, int remotePort) { + // Do not add any code to these methods. They are concrete only to preserve API + // compatibility. + } + + /** + * Initialize the close() state. + * @hide used in java.nio. + */ + public void onClose() { + // Do not add any code to these methods. They are concrete only to preserve API + // compatibility. + } } diff --git a/luni/src/main/java/java/net/SocketOptions.java b/luni/src/main/java/java/net/SocketOptions.java index e23fc97..d0df689 100644 --- a/luni/src/main/java/java/net/SocketOptions.java +++ b/luni/src/main/java/java/net/SocketOptions.java @@ -28,19 +28,27 @@ package java.net; */ public interface SocketOptions { /** - * Number of seconds to wait when closing a socket if there - * is still some buffered data to be sent. + * Number of seconds to wait when closing a socket if there is still some buffered data to be + * sent. * - * <p>If this option is set to 0, the TCP socket is closed forcefully and the - * call to {@code close} returns immediately. + * <p>The option can be set to disabled using {@link #setOption(int, Object)} with a value of + * {@code Boolean.FALSE}. * - * <p>If this option is set to a value greater than 0, the value is interpreted - * as the number of seconds to wait. If all data could be sent - * during this time, the socket is closed normally. Otherwise the connection will be - * closed forcefully. + * <p>If this option is set to 0, the TCP socket is closed forcefully and the call to + * {@code close} returns immediately. * - * <p>Valid values for this option are in the range 0 to 65535 inclusive. (Larger + * If this option is disabled, closing a socket will return immediately and the close will be + * handled in the background. + * + * <p>If this option is set to a value greater than 0, the value is interpreted as the number of + * seconds to wait. If all data could be sent during this time, the socket is closed normally. + * Otherwise the connection will be closed forcefully. + * + * <p>Valid numeric values for this option are in the range 0 to 65535 inclusive. (Larger * timeouts will be treated as 65535s timeouts; roughly 18 hours.) + * + * <p>This option is intended for use with sockets in blocking mode. The behavior of this option + * for non-blocking sockets is undefined. */ public static final int SO_LINGER = 128; @@ -54,16 +62,21 @@ public interface SocketOptions { public static final int SO_TIMEOUT = 4102; /** - * This boolean option specifies whether data is sent immediately on this socket. - * As a side-effect this could lead to low packet efficiency. The - * socket implementation uses the Nagle's algorithm to try to reach a higher - * packet efficiency if this option is disabled. + * This boolean option specifies whether data is sent immediately on this socket or buffered. + * <p> + * If set to {@code Boolean.TRUE} the Nagle algorithm is disabled and there is no buffering. + * This could lead to low packet efficiency. When set to {@code Boolean.FALSE} the the socket + * implementation uses buffering to try to reach a higher packet efficiency. + * + * <p>See <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122: Requirements for Internet + * Hosts -- Communication Layers</a> for more information about buffering and the Nagle + * algorithm. */ public static final int TCP_NODELAY = 1; /** * This is an IPv4-only socket option whose functionality is subsumed by - * {@link #IP_MULTICAST_IF} and not implemented on Android. + * {@link #IP_MULTICAST_IF2} and not implemented on Android. */ public static final int IP_MULTICAST_IF = 16; @@ -73,9 +86,18 @@ public interface SocketOptions { public static final int SO_BINDADDR = 15; /** - * This boolean option specifies whether a reuse of a local address is allowed even - * if another socket is not yet removed by the operating system. It's only - * available on a {@code MulticastSocket}. + * This boolean option specifies whether a reuse of a local address is allowed when another + * socket has not yet been removed by the operating system. + * + * <p>For connection-oriented sockets, if this option is disabled and if there is another socket + * in state TIME_WAIT on a given address then another socket binding to that address would fail. + * Setting this value after a socket is bound has no effect. + * + * <p>For datagram sockets this option determines whether several sockets can listen on the + * same address; when enabled each socket will receive a copy of the datagram. + * + * <p>See <a href="https://www.ietf.org/rfc/rfc793.txt">RFC 793: Transmission Control Protocol + * </a> for more information about socket re-use. */ public static final int SO_REUSEADDR = 4; @@ -93,11 +115,18 @@ public interface SocketOptions { * This is a hint to the kernel; the kernel may use a larger buffer. * * <p>For datagram sockets, packets larger than this value will be discarded. + * + * <p>See <a href="http://www.ietf.org/rfc/rfc1323.txt">RFC1323: TCP Extensions for High + * Performance</a> for more information about TCP/IP buffering. */ public static final int SO_RCVBUF = 4098; /** - * This boolean option specifies whether the kernel sends keepalive messages. + * This boolean option specifies whether the kernel sends keepalive messages on + * connection-oriented sockets. + * + * <p>See <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122: Requirements for Internet + * Hosts -- Communication Layers</a> for more information on keep-alive. */ public static final int SO_KEEPALIVE = 8; @@ -114,15 +143,18 @@ public interface SocketOptions { /** * This boolean option specifies whether the local loopback of multicast packets is - * enabled or disabled. This option is enabled by default on multicast - * sockets. Note that the sense of this option in Java is the - * <i>opposite</i> of the underlying Unix {@code IP_MULTICAST_LOOP}. - * See {@link MulticastSocket#setLoopbackMode}. + * enabled or disabled. This loopback is enabled by default on multicast sockets. + * + * <p>See <a href="http://tools.ietf.org/rfc/rfc1112.txt">RFC 1112: Host Extensions for IP + * Multicasting</a> for more information about IP multicast. + * + * <p>See {@link MulticastSocket#setLoopbackMode}. */ public static final int IP_MULTICAST_LOOP = 18; /** - * This boolean option can be used to enable broadcasting on datagram sockets. + * This boolean option can be used to enable or disable broadcasting on datagram sockets. This + * option must be enabled to send broadcast messages. The default value is false. */ public static final int SO_BROADCAST = 32; @@ -135,6 +167,9 @@ public interface SocketOptions { /** * This integer option sets the outgoing interface for multicast packets * using an interface index. + * + * <p>See <a href="http://tools.ietf.org/rfc/rfc1112.txt">RFC 1112: Host Extensions for IP + * Multicasting</a> for more information about IP multicast. */ public static final int IP_MULTICAST_IF2 = 31; diff --git a/luni/src/main/java/java/net/URI.java b/luni/src/main/java/java/net/URI.java index 6b7b1da..f206473 100644 --- a/luni/src/main/java/java/net/URI.java +++ b/luni/src/main/java/java/net/URI.java @@ -461,11 +461,14 @@ public final class URI implements Comparable<URI>, Serializable { if (index < (temp.length() - 1)) { // port part is not empty try { - tempPort = Integer.parseInt(temp.substring(index + 1)); - if (tempPort < 0) { + char firstPortChar = temp.charAt(index + 1); + if (firstPortChar >= '0' && firstPortChar <= '9') { + // allow only digits, no signs + tempPort = Integer.parseInt(temp.substring(index + 1)); + } else { if (forceServer) { throw new URISyntaxException(authority, - "Invalid port number", hostIndex + index + 1); + "Invalid port number", hostIndex + index + 1); } return; } @@ -766,33 +769,51 @@ public final class URI implements Comparable<URI>, Serializable { } /** - * Returns true if {@code first} and {@code second} are equal after - * unescaping hex sequences like %F1 and %2b. + * Returns true if the given URI escaped strings {@code first} and {@code second} are + * equal. + * + * TODO: This method assumes that both strings are escaped using the same escape rules + * yet it still performs case insensitive comparison of the escaped sequences. + * Why is this necessary ? We can just replace it with first.equals(second) + * otherwise. */ private boolean escapedEquals(String first, String second) { - if (first.indexOf('%') != second.indexOf('%')) { - return first.equals(second); + // This length test isn't a micro-optimization. We need it because we sometimes + // calculate the number of characters to match based on the length of the second + // string. If the second string is shorter than the first, we might attempt to match + // 0 chars, and regionMatches is specified to return true in that case. + if (first.length() != second.length()) { + return false; } - int index, prevIndex = 0; - while ((index = first.indexOf('%', prevIndex)) != -1 - && second.indexOf('%', prevIndex) == index) { - boolean match = first.substring(prevIndex, index).equals( - second.substring(prevIndex, index)); - if (!match) { + int prevIndex = 0; + while (true) { + int index = first.indexOf('%', prevIndex); + int index1 = second.indexOf('%', prevIndex); + if (index != index1) { + return false; + } + + // index == index1 from this point on. + + if (index == -1) { + // No more escapes, match the remainder of the string + // normally. + return first.regionMatches(prevIndex, second, prevIndex, + second.length() - prevIndex); + } + + if (!first.regionMatches(prevIndex, second, prevIndex, (index - prevIndex))) { return false; } - match = first.substring(index + 1, index + 3).equalsIgnoreCase( - second.substring(index + 1, index + 3)); - if (!match) { + if (!first.regionMatches(true /* ignore case */, index + 1, second, index + 1, 2)) { return false; } index += 3; prevIndex = index; } - return first.substring(prevIndex).equals(second.substring(prevIndex)); } @Override public boolean equals(Object o) { diff --git a/luni/src/main/java/java/net/URLConnection.java b/luni/src/main/java/java/net/URLConnection.java index 74c15ce..2fb3f45 100644 --- a/luni/src/main/java/java/net/URLConnection.java +++ b/luni/src/main/java/java/net/URLConnection.java @@ -308,9 +308,8 @@ public abstract class URLConnection { /** * Returns the content length in bytes specified by the response header field - * {@code content-length} or {@code -1} if this field is not set. - * - * @return the value of the response header field {@code content-length}. + * {@code content-length} or {@code -1} if this field is not set or cannot be represented as an + * {@code int}. */ public int getContentLength() { return getHeaderFieldInt("Content-Length", -1); @@ -531,7 +530,7 @@ public abstract class URLConnection { /** * Returns the specified header value as a number. Returns the {@code * defaultValue} if no such header field could be found or the value could - * not be parsed as an {@code Integer}. + * not be parsed as an {@code int}. * * @param field * the header field name whose value is needed. diff --git a/luni/src/main/java/java/net/URLStreamHandler.java b/luni/src/main/java/java/net/URLStreamHandler.java index 8a6c264..d21bb9c 100644 --- a/luni/src/main/java/java/net/URLStreamHandler.java +++ b/luni/src/main/java/java/net/URLStreamHandler.java @@ -131,9 +131,11 @@ public abstract class URLStreamHandler { host = spec.substring(hostStart, hostEnd); int portStart = hostEnd + 1; if (portStart < fileStart) { - port = Integer.parseInt(spec.substring(portStart, fileStart)); - if (port < 0) { - throw new IllegalArgumentException("port < 0: " + port); + char firstPortChar = spec.charAt(portStart); + if (firstPortChar >= '0' && firstPortChar <= '9') { + port = Integer.parseInt(spec.substring(portStart, fileStart)); + } else { + throw new IllegalArgumentException("invalid port: " + port); } } path = null; diff --git a/luni/src/main/java/java/nio/Buffer.java b/luni/src/main/java/java/nio/Buffer.java index 9b7be52..53ad171 100644 --- a/luni/src/main/java/java/nio/Buffer.java +++ b/luni/src/main/java/java/nio/Buffer.java @@ -85,22 +85,16 @@ public abstract class Buffer { /** * For direct buffers, the effective address of the data; zero otherwise. * This is set in the constructor. - * TODO: make this final at the cost of loads of extra constructors? [how many?] */ - long effectiveDirectAddress; + final long effectiveDirectAddress; - /** - * For direct buffers, the underlying MemoryBlock; null otherwise. - */ - final MemoryBlock block; - - Buffer(int elementSizeShift, int capacity, MemoryBlock block) { + Buffer(int elementSizeShift, int capacity, long effectiveDirectAddress) { this._elementSizeShift = elementSizeShift; if (capacity < 0) { throw new IllegalArgumentException("capacity < 0: " + capacity); } this.capacity = this.limit = capacity; - this.block = block; + this.effectiveDirectAddress = effectiveDirectAddress; } /** @@ -296,7 +290,7 @@ public abstract class Buffer { * the new limit, must not be negative and not greater than * capacity. * @return this buffer. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if <code>newLimit</code> is invalid. */ public final Buffer limit(int newLimit) { @@ -344,7 +338,7 @@ public abstract class Buffer { * the new position, must be not negative and not greater than * limit. * @return this buffer. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if <code>newPosition</code> is invalid. */ public final Buffer position(int newPosition) { @@ -377,7 +371,7 @@ public abstract class Buffer { * Resets the position of this buffer to the <code>mark</code>. * * @return this buffer. - * @exception InvalidMarkException + * @throws InvalidMarkException * if the mark is not set. */ public final Buffer reset() { @@ -409,4 +403,9 @@ public abstract class Buffer { return getClass().getName() + "[position=" + position + ",limit=" + limit + ",capacity=" + capacity + "]"; } + + /** @hide for testing only */ + public final int getElementSizeShift() { + return _elementSizeShift; + } } diff --git a/luni/src/main/java/java/nio/ByteArrayBuffer.java b/luni/src/main/java/java/nio/ByteArrayBuffer.java index e8d7ecc..6a273ed 100644 --- a/luni/src/main/java/java/nio/ByteArrayBuffer.java +++ b/luni/src/main/java/java/nio/ByteArrayBuffer.java @@ -38,7 +38,7 @@ final class ByteArrayBuffer extends ByteBuffer { } private ByteArrayBuffer(int capacity, byte[] backingArray, int arrayOffset, boolean isReadOnly) { - super(capacity, null); + super(capacity, 0); this.backingArray = backingArray; this.arrayOffset = arrayOffset; this.isReadOnly = isReadOnly; diff --git a/luni/src/main/java/java/nio/ByteBuffer.java b/luni/src/main/java/java/nio/ByteBuffer.java index 456a309..61093fa 100644 --- a/luni/src/main/java/java/nio/ByteBuffer.java +++ b/luni/src/main/java/java/nio/ByteBuffer.java @@ -69,7 +69,11 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer if (capacity < 0) { throw new IllegalArgumentException("capacity < 0: " + capacity); } - return new DirectByteBuffer(MemoryBlock.allocate(capacity), capacity, 0, false, null); + // Ensure alignment by 8. + MemoryBlock memoryBlock = MemoryBlock.allocate(capacity + 7); + long address = memoryBlock.toLong(); + long alignedAddress = (address + 7) & ~(long)7; + return new DirectByteBuffer(memoryBlock, capacity, (int)(alignedAddress - address), false, null); } /** @@ -101,7 +105,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the length, must not be negative and not greater than * {@code array.length - start}. * @return the created byte buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code byteCount} is invalid. */ public static ByteBuffer wrap(byte[] array, int start, int byteCount) { @@ -112,17 +116,17 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer return buf; } - ByteBuffer(int capacity, MemoryBlock block) { - super(0, capacity, block); + ByteBuffer(int capacity, long effectiveDirectAddress) { + super(0, capacity, effectiveDirectAddress); } /** * Returns the byte array which this buffer is based on, if there is one. * * @return the byte array which this buffer is based on. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if this buffer is based on a read-only array. - * @exception UnsupportedOperationException + * @throws UnsupportedOperationException * if this buffer is not based on an array. */ @Override public final byte[] array() { @@ -137,9 +141,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * position of the buffer. * * @return the offset of the byte array which this buffer is based on. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if this buffer is based on a read-only array. - * @exception UnsupportedOperationException + * @throws UnsupportedOperationException * if this buffer is not based on an array. */ @Override public final int arrayOffset() { @@ -260,7 +264,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * cleared. * * @return {@code this} - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer compact(); @@ -274,7 +278,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @return a negative value if this is less than {@code other}; 0 if this * equals to {@code other}; a positive value if this is greater * than {@code other}. - * @exception ClassCastException + * @throws ClassCastException * if {@code other} is not a byte buffer. */ @Override public int compareTo(ByteBuffer otherBuffer) { @@ -350,7 +354,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * Returns the byte at the current position and increases the position by 1. * * @return the byte at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is equal or greater than limit. */ public abstract byte get(); @@ -365,7 +369,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param dst * the destination byte array. * @return {@code this} - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code dst.length} is greater than {@code remaining()}. */ public ByteBuffer get(byte[] dst) { @@ -386,8 +390,8 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the number of bytes to read, must not be negative and not * greater than {@code dst.length - dstOffset} * @return {@code this} - * @exception IndexOutOfBoundsException if {@code dstOffset < 0 || byteCount < 0} - * @exception BufferUnderflowException if {@code byteCount > remaining()} + * @throws IndexOutOfBoundsException if {@code dstOffset < 0 || byteCount < 0} + * @throws BufferUnderflowException if {@code byteCount > remaining()} */ public ByteBuffer get(byte[] dst, int dstOffset, int byteCount) { Arrays.checkOffsetAndCount(dst.length, dstOffset, byteCount); @@ -406,7 +410,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param index * the index, must not be negative and less than limit. * @return the byte at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. */ public abstract byte get(int index); @@ -418,7 +422,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * according to the current byte order and returned. * * @return the char at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is greater than {@code limit - 2}. */ public abstract char getChar(); @@ -434,7 +438,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the index, must not be negative and equal or less than * {@code limit - 2}. * @return the char at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. */ public abstract char getChar(int index); @@ -447,7 +451,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * according to the current byte order and returned. * * @return the double at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is greater than {@code limit - 8}. */ public abstract double getDouble(); @@ -463,7 +467,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the index, must not be negative and equal or less than * {@code limit - 8}. * @return the double at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. */ public abstract double getDouble(int index); @@ -476,7 +480,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * according to the current byte order and returned. * * @return the float at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is greater than {@code limit - 4}. */ public abstract float getFloat(); @@ -492,7 +496,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the index, must not be negative and equal or less than * {@code limit - 4}. * @return the float at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. */ public abstract float getFloat(int index); @@ -504,7 +508,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * according to the current byte order and returned. * * @return the int at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is greater than {@code limit - 4}. */ public abstract int getInt(); @@ -520,7 +524,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the index, must not be negative and equal or less than * {@code limit - 4}. * @return the int at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. */ public abstract int getInt(int index); @@ -532,7 +536,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * according to the current byte order and returned. * * @return the long at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is greater than {@code limit - 8}. */ public abstract long getLong(); @@ -548,7 +552,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the index, must not be negative and equal or less than * {@code limit - 8}. * @return the long at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. */ public abstract long getLong(int index); @@ -560,7 +564,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * according to the current byte order and returned. * * @return the short at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is greater than {@code limit - 2}. */ public abstract short getShort(); @@ -576,7 +580,7 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the index, must not be negative and equal or less than * {@code limit - 2}. * @return the short at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. */ public abstract short getShort(int index); @@ -609,6 +613,30 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer @Override public abstract boolean isDirect(); /** + * Indicates whether this buffer is still accessible. + * + * @return {@code true} if this buffer is accessible, {@code false} if the + * buffer was made inaccessible (e.g. freed) and should not be used. + * @hide + */ + public boolean isAccessible() { + return true; + } + + /** + * Sets buffer accessibility (only supported for direct byte buffers). If + * {@code accessible} is {@code false}, {@link #isAccessible} will return + * false, and any attempt to access the buffer will throw an exception. If + * {@code true}, the buffer will become useable again, unless it has been + * freed. + * + * @hide + */ + public void setAccessible(boolean accessible) { + throw new UnsupportedOperationException(); + } + + /** * Returns the byte order used by this buffer when converting bytes from/to * other primitive types. * <p> @@ -667,9 +695,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param b * the byte to write. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is equal or greater than limit. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer put(byte b); @@ -684,9 +712,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param src * the source byte array. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code src.length}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public final ByteBuffer put(byte[] src) { @@ -707,11 +735,11 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * the number of bytes to write, must not be negative and not * greater than {@code src.length - srcOffset}. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code byteCount}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code srcOffset} or {@code byteCount} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public ByteBuffer put(byte[] src, int srcOffset, int byteCount) { @@ -733,21 +761,27 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param src * the source byte buffer. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code src.remaining()} is greater than this buffer's * {@code remaining()}. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if {@code src} is this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public ByteBuffer put(ByteBuffer src) { + if (!isAccessible()) { + throw new IllegalStateException("buffer is inaccessible"); + } if (isReadOnly()) { throw new ReadOnlyBufferException(); } if (src == this) { throw new IllegalArgumentException("src == this"); } + if (!src.isAccessible()) { + throw new IllegalStateException("src buffer is inaccessible"); + } int srcByteCount = src.remaining(); if (srcByteCount > remaining()) { throw new BufferOverflowException(); @@ -782,9 +816,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param b * the byte to write. * @return {@code this} - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer put(int index, byte b); @@ -798,9 +832,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the char to write. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is greater than {@code limit - 2}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putChar(char value); @@ -817,9 +851,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the char to write. * @return {@code this} - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putChar(int index, char value); @@ -833,9 +867,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the double to write. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is greater than {@code limit - 8}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putDouble(double value); @@ -852,9 +886,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the double to write. * @return {@code this} - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putDouble(int index, double value); @@ -868,9 +902,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the float to write. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is greater than {@code limit - 4}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putFloat(float value); @@ -887,9 +921,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the float to write. * @return {@code this} - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putFloat(int index, float value); @@ -903,9 +937,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the int to write. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is greater than {@code limit - 4}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putInt(int value); @@ -922,9 +956,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the int to write. * @return {@code this} - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putInt(int index, int value); @@ -938,9 +972,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the long to write. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is greater than {@code limit - 8}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putLong(long value); @@ -957,9 +991,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the long to write. * @return {@code this} - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putLong(int index, long value); @@ -973,9 +1007,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the short to write. * @return {@code this} - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is greater than {@code limit - 2}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putShort(short value); @@ -992,9 +1026,9 @@ public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer * @param value * the short to write. * @return {@code this} - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if {@code index} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ByteBuffer putShort(int index, short value); diff --git a/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java index 16d0688..9d06cce 100644 --- a/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java +++ b/luni/src/main/java/java/nio/ByteBufferAsCharBuffer.java @@ -42,10 +42,9 @@ final class ByteBufferAsCharBuffer extends CharBuffer { } private ByteBufferAsCharBuffer(ByteBuffer byteBuffer) { - super(byteBuffer.capacity() / SizeOf.CHAR); + super(byteBuffer.capacity() / SizeOf.CHAR, byteBuffer.effectiveDirectAddress); this.byteBuffer = byteBuffer; this.byteBuffer.clear(); - this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress; } @Override diff --git a/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java index 044bf59..8271daf 100644 --- a/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java +++ b/luni/src/main/java/java/nio/ByteBufferAsDoubleBuffer.java @@ -42,10 +42,9 @@ final class ByteBufferAsDoubleBuffer extends DoubleBuffer { } private ByteBufferAsDoubleBuffer(ByteBuffer byteBuffer) { - super(byteBuffer.capacity() / SizeOf.DOUBLE); + super(byteBuffer.capacity() / SizeOf.DOUBLE, byteBuffer.effectiveDirectAddress); this.byteBuffer = byteBuffer; this.byteBuffer.clear(); - this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress; } @Override diff --git a/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java index a67affe..eaf2b8f 100644 --- a/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java +++ b/luni/src/main/java/java/nio/ByteBufferAsFloatBuffer.java @@ -41,10 +41,9 @@ final class ByteBufferAsFloatBuffer extends FloatBuffer { } ByteBufferAsFloatBuffer(ByteBuffer byteBuffer) { - super(byteBuffer.capacity() / SizeOf.FLOAT); + super(byteBuffer.capacity() / SizeOf.FLOAT, byteBuffer.effectiveDirectAddress); this.byteBuffer = byteBuffer; this.byteBuffer.clear(); - this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress; } @Override diff --git a/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java index a3211b8..f584f1a 100644 --- a/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java +++ b/luni/src/main/java/java/nio/ByteBufferAsIntBuffer.java @@ -42,10 +42,9 @@ final class ByteBufferAsIntBuffer extends IntBuffer { } private ByteBufferAsIntBuffer(ByteBuffer byteBuffer) { - super(byteBuffer.capacity() / SizeOf.INT); + super(byteBuffer.capacity() / SizeOf.INT, byteBuffer.effectiveDirectAddress); this.byteBuffer = byteBuffer; this.byteBuffer.clear(); - this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress; } @Override diff --git a/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java index 550c675..8afa66e 100644 --- a/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java +++ b/luni/src/main/java/java/nio/ByteBufferAsLongBuffer.java @@ -42,10 +42,9 @@ final class ByteBufferAsLongBuffer extends LongBuffer { } private ByteBufferAsLongBuffer(ByteBuffer byteBuffer) { - super(byteBuffer.capacity() / SizeOf.LONG); + super(byteBuffer.capacity() / SizeOf.LONG, byteBuffer.effectiveDirectAddress); this.byteBuffer = byteBuffer; this.byteBuffer.clear(); - this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress; } @Override diff --git a/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java b/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java index ff81409..52afc0e 100644 --- a/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java +++ b/luni/src/main/java/java/nio/ByteBufferAsShortBuffer.java @@ -41,10 +41,9 @@ final class ByteBufferAsShortBuffer extends ShortBuffer { } private ByteBufferAsShortBuffer(ByteBuffer byteBuffer) { - super(byteBuffer.capacity() / SizeOf.SHORT); + super(byteBuffer.capacity() / SizeOf.SHORT, byteBuffer.effectiveDirectAddress); this.byteBuffer = byteBuffer; this.byteBuffer.clear(); - this.effectiveDirectAddress = byteBuffer.effectiveDirectAddress; } @Override diff --git a/luni/src/main/java/java/nio/CharArrayBuffer.java b/luni/src/main/java/java/nio/CharArrayBuffer.java index 245a799..8f9fd9b 100644 --- a/luni/src/main/java/java/nio/CharArrayBuffer.java +++ b/luni/src/main/java/java/nio/CharArrayBuffer.java @@ -33,7 +33,7 @@ final class CharArrayBuffer extends CharBuffer { } private CharArrayBuffer(int capacity, char[] backingArray, int arrayOffset, boolean isReadOnly) { - super(capacity); + super(capacity, 0); this.backingArray = backingArray; this.arrayOffset = arrayOffset; this.isReadOnly = isReadOnly; diff --git a/luni/src/main/java/java/nio/CharBuffer.java b/luni/src/main/java/java/nio/CharBuffer.java index 92cab01..748a787 100644 --- a/luni/src/main/java/java/nio/CharBuffer.java +++ b/luni/src/main/java/java/nio/CharBuffer.java @@ -83,7 +83,7 @@ public abstract class CharBuffer extends Buffer implements * the length, must not be negative and not greater than * {@code array.length - start}. * @return the created char buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code charCount} is invalid. */ public static CharBuffer wrap(char[] array, int start, int charCount) { @@ -124,7 +124,7 @@ public abstract class CharBuffer extends Buffer implements * the end index, must be no less than {@code start} and no * greater than {@code cs.length()}. * @return the created char buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code end} is invalid. */ public static CharBuffer wrap(CharSequence cs, int start, int end) { @@ -137,8 +137,8 @@ public abstract class CharBuffer extends Buffer implements return result; } - CharBuffer(int capacity) { - super(1, capacity, null); + CharBuffer(int capacity, long effectiveDirectAddress) { + super(1, capacity, effectiveDirectAddress); } public final char[] array() { @@ -165,17 +165,8 @@ public abstract class CharBuffer extends Buffer implements public abstract CharBuffer asReadOnlyBuffer(); /** - * Returns the character located at the specified index in the buffer. The - * index value is referenced from the current buffer position. - * - * @param index - * the index referenced from the current buffer position. It must - * not be less than zero but less than the value obtained from a - * call to {@code remaining()}. - * @return the character located at the specified index (referenced from the - * current position) in the buffer. - * @exception IndexOutOfBoundsException - * if the index is invalid. + * Returns the character located at the given offset <i>relative to the current position</i>. + * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= remaining()}. */ public final char charAt(int index) { if (index < 0 || index >= remaining()) { @@ -192,7 +183,7 @@ public abstract class CharBuffer extends Buffer implements * {@code remaining()}; the limit is set to capacity; the mark is cleared. * * @return this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract CharBuffer compact(); @@ -206,7 +197,7 @@ public abstract class CharBuffer extends Buffer implements * @return a negative value if this is less than {@code otherBuffer}; 0 if * this equals to {@code otherBuffer}; a positive value if this is * greater than {@code otherBuffer}. - * @exception ClassCastException + * @throws ClassCastException * if {@code otherBuffer} is not a char buffer. */ public int compareTo(CharBuffer otherBuffer) { @@ -278,7 +269,7 @@ public abstract class CharBuffer extends Buffer implements * Returns the char at the current position and increases the position by 1. * * @return the char at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is equal or greater than limit. */ public abstract char get(); @@ -293,7 +284,7 @@ public abstract class CharBuffer extends Buffer implements * @param dst * the destination char array. * @return this buffer. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code dst.length} is greater than {@code remaining()}. */ public CharBuffer get(char[] dst) { @@ -314,9 +305,9 @@ public abstract class CharBuffer extends Buffer implements * The number of chars to read, must be no less than zero and no * greater than {@code dst.length - dstOffset}. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code dstOffset} or {@code charCount} is invalid. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code charCount} is greater than {@code remaining()}. */ public CharBuffer get(char[] dst, int dstOffset, int charCount) { @@ -336,7 +327,7 @@ public abstract class CharBuffer extends Buffer implements * @param index * the index, must not be negative and less than limit. * @return a char at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. */ public abstract char get(int index); @@ -422,9 +413,9 @@ public abstract class CharBuffer extends Buffer implements * @param c * the char to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is equal or greater than limit. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract CharBuffer put(char c); @@ -439,9 +430,9 @@ public abstract class CharBuffer extends Buffer implements * @param src * the source char array. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code src.length}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public final CharBuffer put(char[] src) { @@ -462,11 +453,11 @@ public abstract class CharBuffer extends Buffer implements * the number of chars to write, must be no less than zero and no * greater than {@code src.length - srcOffset}. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code charCount}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code srcOffset} or {@code charCount} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public CharBuffer put(char[] src, int srcOffset, int charCount) { @@ -488,12 +479,12 @@ public abstract class CharBuffer extends Buffer implements * @param src * the source char buffer. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code src.remaining()} is greater than this buffer's * {@code remaining()}. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if {@code src} is this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public CharBuffer put(CharBuffer src) { @@ -522,9 +513,9 @@ public abstract class CharBuffer extends Buffer implements * @param c * the char to write. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract CharBuffer put(int index, char c); @@ -539,9 +530,9 @@ public abstract class CharBuffer extends Buffer implements * @param str * the string to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than the length of string. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public final CharBuffer put(String str) { @@ -561,11 +552,11 @@ public abstract class CharBuffer extends Buffer implements * the last char to write (excluding), must be less than * {@code start} and not greater than {@code str.length()}. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code end - start}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code end} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public CharBuffer put(String str, int start, int end) { @@ -625,7 +616,7 @@ public abstract class CharBuffer extends Buffer implements * {@code remaining()}. * @return a new char buffer represents a sub-sequence of this buffer's * current remaining content. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code end} is invalid. */ public abstract CharBuffer subSequence(int start, int end); @@ -649,9 +640,9 @@ public abstract class CharBuffer extends Buffer implements * @param c * the char to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is equal or greater than limit. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public CharBuffer append(char c) { @@ -670,9 +661,9 @@ public abstract class CharBuffer extends Buffer implements * @param csq * the {@code CharSequence} to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than the length of csq. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public CharBuffer append(CharSequence csq) { @@ -695,11 +686,11 @@ public abstract class CharBuffer extends Buffer implements * the last char to write (excluding), must be less than * {@code start} and not greater than {@code csq.length()}. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code end - start}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code end} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public CharBuffer append(CharSequence csq, int start, int end) { diff --git a/luni/src/main/java/java/nio/CharSequenceAdapter.java b/luni/src/main/java/java/nio/CharSequenceAdapter.java index f686827..fb8a0f1 100644 --- a/luni/src/main/java/java/nio/CharSequenceAdapter.java +++ b/luni/src/main/java/java/nio/CharSequenceAdapter.java @@ -42,7 +42,7 @@ final class CharSequenceAdapter extends CharBuffer { final CharSequence sequence; CharSequenceAdapter(CharSequence chseq) { - super(chseq.length()); + super(chseq.length(), 0); sequence = chseq; } diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java index 39f2128..9008637 100644 --- a/luni/src/main/java/java/nio/DatagramChannelImpl.java +++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java @@ -17,15 +17,18 @@ package java.nio; +import android.system.ErrnoException; import java.io.FileDescriptor; -import java.io.IOException; import java.io.InterruptedIOException; +import java.io.IOException; import java.net.ConnectException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.DatagramSocketImpl; +import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.NetworkInterface; import java.net.PlainDatagramSocketImpl; import java.net.SocketAddress; import java.net.SocketException; @@ -35,8 +38,10 @@ import java.nio.channels.DatagramChannel; import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.spi.SelectorProvider; +import java.nio.channels.UnresolvedAddressException; +import java.nio.channels.UnsupportedAddressTypeException; import java.util.Arrays; -import libcore.io.ErrnoException; +import java.util.Set; import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; @@ -50,10 +55,13 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann private final FileDescriptor fd; // Our internal DatagramSocket. - private DatagramSocket socket = null; + private DatagramSocket socket; + + // The remote address to be connected. + InetSocketAddress connectAddress; - // The address to be connected. - InetSocketAddress connectAddress = null; + // The local address. + InetAddress localAddress; // local port private int localPort; @@ -98,19 +106,38 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann } /** - * @see java.nio.channels.DatagramChannel#isConnected() + * Initialise the isBound, localAddress and localPort state from the file descriptor. Used when + * some or all of the bound state has been left to the OS to decide, or when the Socket handled + * bind() or connect(). + * + * @param updateSocketState + * if the associated socket (if present) needs to be updated + * @hide used to sync state, non-private to avoid synthetic method */ + void onBind(boolean updateSocketState) { + SocketAddress sa; + try { + sa = Libcore.os.getsockname(fd); + } catch (ErrnoException errnoException) { + throw new AssertionError(errnoException); + } + isBound = true; + InetSocketAddress localSocketAddress = (InetSocketAddress) sa; + localAddress = localSocketAddress.getAddress(); + localPort = localSocketAddress.getPort(); + if (updateSocketState && socket != null) { + socket.onBind(localAddress, localPort); + } + } + @Override synchronized public boolean isConnected() { return connected; } - /** - * @see java.nio.channels.DatagramChannel#connect(java.net.SocketAddress) - */ @Override synchronized public DatagramChannel connect(SocketAddress address) throws IOException { - // must open + // must be open checkOpen(); // status must be un-connected. if (connected) { @@ -119,43 +146,71 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann // check the address InetSocketAddress inetSocketAddress = SocketChannelImpl.validateAddress(address); + InetAddress remoteAddress = inetSocketAddress.getAddress(); + int remotePort = inetSocketAddress.getPort(); try { begin(); - IoBridge.connect(fd, inetSocketAddress.getAddress(), inetSocketAddress.getPort()); + IoBridge.connect(fd, remoteAddress, remotePort); } catch (ConnectException e) { // ConnectException means connect fail, not exception } finally { end(true); } - // set the connected address. - connectAddress = inetSocketAddress; - connected = true; - isBound = true; + // connect() performs a bind() if an explicit bind() was not performed. Keep the local + // address state held by the channel and the socket up to date. + if (!isBound) { + onBind(true /* updateSocketState */); + } + + // Keep the connected state held by the channel and the socket up to date. + onConnect(remoteAddress, remotePort, true /* updateSocketState */); return this; } /** - * @see java.nio.channels.DatagramChannel#disconnect() + * Initialize the state associated with being connected, optionally syncing the socket if there + * is one. + * @hide used to sync state, non-private to avoid synthetic method */ + void onConnect(InetAddress remoteAddress, int remotePort, boolean updateSocketState) { + connected = true; + connectAddress = new InetSocketAddress(remoteAddress, remotePort); + if (updateSocketState && socket != null) { + socket.onConnect(remoteAddress, remotePort); + } + } + @Override synchronized public DatagramChannel disconnect() throws IOException { if (!isConnected() || !isOpen()) { return this; } - connected = false; - connectAddress = null; + + // Keep the disconnected state held by the channel and the socket up to date. + onDisconnect(true /* updateSocketState */); + try { Libcore.os.connect(fd, InetAddress.UNSPECIFIED, 0); } catch (ErrnoException errnoException) { throw errnoException.rethrowAsIOException(); } - if (socket != null) { - socket.disconnect(); - } return this; } + /** + * Initialize the state associated with being disconnected, optionally syncing the socket if + * there is one. + * @hide used to sync state, non-private to avoid synthetic method + */ + void onDisconnect(boolean updateSocketState) { + connected = false; + connectAddress = null; + if (updateSocketState && socket != null && socket.isConnected()) { + socket.onDisconnect(); + } + } + @Override public SocketAddress receive(ByteBuffer target) throws IOException { target.checkWritable(); @@ -191,7 +246,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann SocketAddress retAddr = null; DatagramPacket receivePacket; int oldposition = target.position(); - int received = 0; + int received; // TODO: disallow mapped buffers and lose this conditional? if (target.hasArray()) { receivePacket = new DatagramPacket(target.array(), target.position() + target.arrayOffset(), target.remaining()); @@ -200,7 +255,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann } do { received = IoBridge.recvfrom(false, fd, receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength(), 0, receivePacket, isConnected()); - if (receivePacket != null && receivePacket.getAddress() != null) { + if (receivePacket.getAddress() != null) { if (received > 0) { if (target.hasArray()) { target.position(oldposition + received); @@ -220,10 +275,10 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann SocketAddress retAddr = null; DatagramPacket receivePacket = new DatagramPacket(EmptyArray.BYTE, 0); int oldposition = target.position(); - int received = 0; + int received; do { received = IoBridge.recvfrom(false, fd, target, 0, receivePacket, isConnected()); - if (receivePacket != null && receivePacket.getAddress() != null) { + if (receivePacket.getAddress() != null) { // copy the data of received packet if (received > 0) { target.position(oldposition + received); @@ -259,7 +314,9 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann if (sendCount > 0) { source.position(oldPosition + sendCount); } - isBound = true; + if (!isBound) { + onBind(true /* updateSocketState */); + } } finally { end(sendCount >= 0); } @@ -276,7 +333,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann return 0; } - int readCount = 0; + int readCount; if (target.isDirect() || target.hasArray()) { readCount = readImpl(target); if (readCount > 0) { @@ -405,11 +462,12 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann } @Override protected synchronized void implCloseSelectableChannel() throws IOException { - connected = false; + // A closed channel is not connected. + onDisconnect(true /* updateSocketState */); + IoBridge.closeAndSignalBlockedThreads(fd); + if (socket != null && !socket.isClosed()) { - socket.close(); - } else { - IoBridge.closeSocket(fd); + socket.onClose(); } } @@ -420,7 +478,7 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann /* * Status check, must be open. */ - private void checkOpen() throws IOException { + private void checkOpen() throws ClosedChannelException { if (!isOpen()) { throw new ClosedChannelException(); } @@ -460,15 +518,29 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann /* * The internal datagramChannelImpl. */ - private DatagramChannelImpl channelImpl; + private final DatagramChannelImpl channelImpl; /* * Constructor initialize the datagramSocketImpl and datagramChannelImpl */ - DatagramSocketAdapter(DatagramSocketImpl socketimpl, - DatagramChannelImpl channelImpl) { + DatagramSocketAdapter(DatagramSocketImpl socketimpl, DatagramChannelImpl channelImpl) { super(socketimpl); this.channelImpl = channelImpl; + + // Sync state socket state with the channel it is being created from + if (channelImpl.isBound) { + onBind(channelImpl.localAddress, channelImpl.localPort); + } + if (channelImpl.connected) { + onConnect( + channelImpl.connectAddress.getAddress(), + channelImpl.connectAddress.getPort()); + } else { + onDisconnect(); + } + if (!channelImpl.isOpen()) { + onClose(); + } } /* @@ -479,92 +551,78 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann return channelImpl; } - /** - * @see java.net.DatagramSocket#isBound() - */ - @Override - public boolean isBound() { - return channelImpl.isBound; - } - - /** - * @see java.net.DatagramSocket#isConnected() - */ - @Override - public boolean isConnected() { - return channelImpl.isConnected(); - } - - /** - * @see java.net.DatagramSocket#getInetAddress() - */ @Override - public InetAddress getInetAddress() { - if (channelImpl.connectAddress == null) { - return null; - } - return channelImpl.connectAddress.getAddress(); - } - - @Override public InetAddress getLocalAddress() { - try { - return IoBridge.getSocketLocalAddress(channelImpl.fd); - } catch (SocketException ex) { - return null; + public void bind(SocketAddress localAddr) throws SocketException { + if (channelImpl.isConnected()) { + throw new AlreadyConnectedException(); } + super.bind(localAddr); + channelImpl.onBind(false /* updateSocketState */); } - /** - * @see java.net.DatagramSocket#getPort() - */ @Override - public int getPort() { - if (channelImpl.connectAddress == null) { - return -1; + public void connect(SocketAddress peer) throws SocketException { + if (isConnected()) { + // RI compatibility: If the socket is already connected this fails. + throw new IllegalStateException("Socket is already connected."); } - return channelImpl.connectAddress.getPort(); + super.connect(peer); + // Connect may have performed an implicit bind(). Sync up here. + channelImpl.onBind(false /* updateSocketState */); + + InetSocketAddress inetSocketAddress = (InetSocketAddress) peer; + channelImpl.onConnect( + inetSocketAddress.getAddress(), inetSocketAddress.getPort(), + false /* updateSocketState */); } - /** - * @see java.net.DatagramSocket#bind(java.net.SocketAddress) - */ @Override - public void bind(SocketAddress localAddr) throws SocketException { - if (channelImpl.isConnected()) { - throw new AlreadyConnectedException(); + public void connect(InetAddress address, int port) { + // To avoid implementing connect() twice call this.connect(SocketAddress) in preference + // to super.connect(). + try { + connect(new InetSocketAddress(address, port)); + } catch (SocketException e) { + // Ignored - there is nothing we can report here. } - super.bind(localAddr); - channelImpl.isBound = true; } - /** - * @see java.net.DatagramSocket#receive(java.net.DatagramPacket) - */ @Override public void receive(DatagramPacket packet) throws IOException { if (!channelImpl.isBlocking()) { throw new IllegalBlockingModeException(); } + + boolean wasBound = isBound(); super.receive(packet); + if (!wasBound) { + // DatagramSocket.receive() will implicitly bind if it hasn't been done explicitly. + // Sync the channel state with the socket. + channelImpl.onBind(false /* updateSocketState */); + } } - /** - * @see java.net.DatagramSocket#send(java.net.DatagramPacket) - */ @Override public void send(DatagramPacket packet) throws IOException { if (!channelImpl.isBlocking()) { throw new IllegalBlockingModeException(); } + + // DatagramSocket.send() will implicitly bind if it hasn't been done explicitly. Force + // bind() here so that the channel state stays in sync with the socket. + boolean wasBound = isBound(); super.send(packet); + if (!wasBound) { + // DatagramSocket.send() will implicitly bind if it hasn't been done explicitly. + // Sync the channel state with the socket. + channelImpl.onBind(false /* updateSocketState */); + } } - /** - * @see java.net.DatagramSocket#close() - */ @Override public void close() { synchronized (channelImpl) { + super.close(); if (channelImpl.isOpen()) { try { channelImpl.close(); @@ -572,21 +630,13 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann // Ignore } } - super.close(); } } - /** - * @see java.net.DatagramSocket#disconnect() - */ @Override public void disconnect() { - try { - channelImpl.disconnect(); - } catch (IOException e) { - // Ignore - } super.disconnect(); + channelImpl.onDisconnect(false /* updateSocketState */); } } } diff --git a/luni/src/main/java/java/nio/DirectByteBuffer.java b/luni/src/main/java/java/nio/DirectByteBuffer.java index 1d12f2e..f19fb42 100644 --- a/luni/src/main/java/java/nio/DirectByteBuffer.java +++ b/luni/src/main/java/java/nio/DirectByteBuffer.java @@ -30,15 +30,16 @@ class DirectByteBuffer extends MappedByteBuffer { private final boolean isReadOnly; protected DirectByteBuffer(MemoryBlock block, int capacity, int offset, boolean isReadOnly, MapMode mapMode) { - super(block, capacity, mapMode); + super(block, capacity, mapMode, block.toLong() + offset); long baseSize = block.getSize(); + // We're throwing this exception after we passed a bogus value + // to the superclass constructor, but it doesn't make any + // difference in this case. if (baseSize >= 0 && (capacity + offset) > baseSize) { throw new IllegalArgumentException("capacity + offset > baseSize"); } - this.effectiveDirectAddress = block.toLong() + offset; - this.offset = offset; this.isReadOnly = isReadOnly; } @@ -49,6 +50,7 @@ class DirectByteBuffer extends MappedByteBuffer { } private static DirectByteBuffer copy(DirectByteBuffer other, int markOfOther, boolean isReadOnly) { + other.checkNotFreed(); DirectByteBuffer buf = new DirectByteBuffer(other.block, other.capacity(), other.offset, isReadOnly, other.mapMode); buf.limit = other.limit; buf.position = other.position(); @@ -61,6 +63,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer compact() { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -76,6 +79,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer slice() { + checkNotFreed(); return new DirectByteBuffer(block, remaining(), offset + position, isReadOnly, mapMode); } @@ -84,6 +88,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override byte[] protectedArray() { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -104,6 +109,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public final ByteBuffer get(byte[] dst, int dstOffset, int byteCount) { + checkIsAccessible(); checkGetBounds(1, dst.length, dstOffset, byteCount); this.block.peekByteArray(offset + position, dst, dstOffset, byteCount); position += byteCount; @@ -111,42 +117,49 @@ class DirectByteBuffer extends MappedByteBuffer { } final void get(char[] dst, int dstOffset, int charCount) { + checkIsAccessible(); int byteCount = checkGetBounds(SizeOf.CHAR, dst.length, dstOffset, charCount); this.block.peekCharArray(offset + position, dst, dstOffset, charCount, order.needsSwap); position += byteCount; } final void get(double[] dst, int dstOffset, int doubleCount) { + checkIsAccessible(); int byteCount = checkGetBounds(SizeOf.DOUBLE, dst.length, dstOffset, doubleCount); this.block.peekDoubleArray(offset + position, dst, dstOffset, doubleCount, order.needsSwap); position += byteCount; } final void get(float[] dst, int dstOffset, int floatCount) { + checkIsAccessible(); int byteCount = checkGetBounds(SizeOf.FLOAT, dst.length, dstOffset, floatCount); this.block.peekFloatArray(offset + position, dst, dstOffset, floatCount, order.needsSwap); position += byteCount; } final void get(int[] dst, int dstOffset, int intCount) { + checkIsAccessible(); int byteCount = checkGetBounds(SizeOf.INT, dst.length, dstOffset, intCount); this.block.peekIntArray(offset + position, dst, dstOffset, intCount, order.needsSwap); position += byteCount; } final void get(long[] dst, int dstOffset, int longCount) { + checkIsAccessible(); int byteCount = checkGetBounds(SizeOf.LONG, dst.length, dstOffset, longCount); this.block.peekLongArray(offset + position, dst, dstOffset, longCount, order.needsSwap); position += byteCount; } final void get(short[] dst, int dstOffset, int shortCount) { + checkIsAccessible(); int byteCount = checkGetBounds(SizeOf.SHORT, dst.length, dstOffset, shortCount); this.block.peekShortArray(offset + position, dst, dstOffset, shortCount, order.needsSwap); position += byteCount; } @Override public final byte get() { + checkIsAccessible(); if (position == limit) { throw new BufferUnderflowException(); } @@ -154,11 +167,13 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public final byte get(int index) { + checkIsAccessible(); checkIndex(index); return this.block.peekByte(offset + index); } @Override public final char getChar() { + checkIsAccessible(); int newPosition = position + SizeOf.CHAR; if (newPosition > limit) { throw new BufferUnderflowException(); @@ -169,11 +184,13 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public final char getChar(int index) { + checkIsAccessible(); checkIndex(index, SizeOf.CHAR); return (char) this.block.peekShort(offset + index, order); } @Override public final double getDouble() { + checkIsAccessible(); int newPosition = position + SizeOf.DOUBLE; if (newPosition > limit) { throw new BufferUnderflowException(); @@ -184,11 +201,13 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public final double getDouble(int index) { + checkIsAccessible(); checkIndex(index, SizeOf.DOUBLE); return Double.longBitsToDouble(this.block.peekLong(offset + index, order)); } @Override public final float getFloat() { + checkIsAccessible(); int newPosition = position + SizeOf.FLOAT; if (newPosition > limit) { throw new BufferUnderflowException(); @@ -199,11 +218,13 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public final float getFloat(int index) { + checkIsAccessible(); checkIndex(index, SizeOf.FLOAT); return Float.intBitsToFloat(this.block.peekInt(offset + index, order)); } @Override public final int getInt() { + checkIsAccessible(); int newPosition = position + SizeOf.INT; if (newPosition > limit) { throw new BufferUnderflowException(); @@ -214,11 +235,13 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public final int getInt(int index) { + checkIsAccessible(); checkIndex(index, SizeOf.INT); return this.block.peekInt(offset + index, order); } @Override public final long getLong() { + checkIsAccessible(); int newPosition = position + SizeOf.LONG; if (newPosition > limit) { throw new BufferUnderflowException(); @@ -229,11 +252,13 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public final long getLong(int index) { + checkIsAccessible(); checkIndex(index, SizeOf.LONG); return this.block.peekLong(offset + index, order); } @Override public final short getShort() { + checkIsAccessible(); int newPosition = position + SizeOf.SHORT; if (newPosition > limit) { throw new BufferUnderflowException(); @@ -244,6 +269,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public final short getShort(int index) { + checkIsAccessible(); checkIndex(index, SizeOf.SHORT); return this.block.peekShort(offset + index, order); } @@ -252,35 +278,56 @@ class DirectByteBuffer extends MappedByteBuffer { return true; } + /** @hide */ + @Override public final boolean isAccessible() { + return block.isAccessible(); + } + + /** @hide */ + @Override public void setAccessible(boolean accessible) { + block.setAccessible(accessible); + } + + /** + * Invalidates the buffer. Subsequent operations which touch the inner + * buffer will throw {@link IllegalStateException}. + */ public final void free() { block.free(); } @Override public final CharBuffer asCharBuffer() { + checkNotFreed(); return ByteBufferAsCharBuffer.asCharBuffer(this); } @Override public final DoubleBuffer asDoubleBuffer() { + checkNotFreed(); return ByteBufferAsDoubleBuffer.asDoubleBuffer(this); } @Override public final FloatBuffer asFloatBuffer() { + checkNotFreed(); return ByteBufferAsFloatBuffer.asFloatBuffer(this); } @Override public final IntBuffer asIntBuffer() { + checkNotFreed(); return ByteBufferAsIntBuffer.asIntBuffer(this); } @Override public final LongBuffer asLongBuffer() { + checkNotFreed(); return ByteBufferAsLongBuffer.asLongBuffer(this); } @Override public final ShortBuffer asShortBuffer() { + checkNotFreed(); return ByteBufferAsShortBuffer.asShortBuffer(this); } @Override public ByteBuffer put(byte value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -292,6 +339,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer put(int index, byte value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -301,6 +349,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer put(byte[] src, int srcOffset, int byteCount) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -311,42 +360,49 @@ class DirectByteBuffer extends MappedByteBuffer { } final void put(char[] src, int srcOffset, int charCount) { + checkIsAccessible(); int byteCount = checkPutBounds(SizeOf.CHAR, src.length, srcOffset, charCount); this.block.pokeCharArray(offset + position, src, srcOffset, charCount, order.needsSwap); position += byteCount; } final void put(double[] src, int srcOffset, int doubleCount) { + checkIsAccessible(); int byteCount = checkPutBounds(SizeOf.DOUBLE, src.length, srcOffset, doubleCount); this.block.pokeDoubleArray(offset + position, src, srcOffset, doubleCount, order.needsSwap); position += byteCount; } final void put(float[] src, int srcOffset, int floatCount) { + checkIsAccessible(); int byteCount = checkPutBounds(SizeOf.FLOAT, src.length, srcOffset, floatCount); this.block.pokeFloatArray(offset + position, src, srcOffset, floatCount, order.needsSwap); position += byteCount; } final void put(int[] src, int srcOffset, int intCount) { + checkIsAccessible(); int byteCount = checkPutBounds(SizeOf.INT, src.length, srcOffset, intCount); this.block.pokeIntArray(offset + position, src, srcOffset, intCount, order.needsSwap); position += byteCount; } final void put(long[] src, int srcOffset, int longCount) { + checkIsAccessible(); int byteCount = checkPutBounds(SizeOf.LONG, src.length, srcOffset, longCount); this.block.pokeLongArray(offset + position, src, srcOffset, longCount, order.needsSwap); position += byteCount; } final void put(short[] src, int srcOffset, int shortCount) { + checkIsAccessible(); int byteCount = checkPutBounds(SizeOf.SHORT, src.length, srcOffset, shortCount); this.block.pokeShortArray(offset + position, src, srcOffset, shortCount, order.needsSwap); position += byteCount; } @Override public ByteBuffer putChar(char value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -360,6 +416,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putChar(int index, char value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -369,6 +426,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putDouble(double value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -382,6 +440,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putDouble(int index, double value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -391,6 +450,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putFloat(float value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -404,6 +464,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putFloat(int index, float value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -413,6 +474,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putInt(int value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -426,6 +488,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putInt(int index, int value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -435,6 +498,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putLong(long value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -448,6 +512,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putLong(int index, long value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -457,6 +522,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putShort(short value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -470,6 +536,7 @@ class DirectByteBuffer extends MappedByteBuffer { } @Override public ByteBuffer putShort(int index, short value) { + checkIsAccessible(); if (isReadOnly) { throw new ReadOnlyBufferException(); } @@ -477,4 +544,18 @@ class DirectByteBuffer extends MappedByteBuffer { this.block.pokeShort(offset + index, value, order); return this; } + + private void checkIsAccessible() { + checkNotFreed(); + if (!block.isAccessible()) { + throw new IllegalStateException("buffer is inaccessible"); + } + } + + private void checkNotFreed() { + if (block.isFreed()) { + throw new IllegalStateException("buffer was freed"); + } + } + } diff --git a/luni/src/main/java/java/nio/DoubleArrayBuffer.java b/luni/src/main/java/java/nio/DoubleArrayBuffer.java index f8e4e59..a2d9e79 100644 --- a/luni/src/main/java/java/nio/DoubleArrayBuffer.java +++ b/luni/src/main/java/java/nio/DoubleArrayBuffer.java @@ -33,7 +33,7 @@ final class DoubleArrayBuffer extends DoubleBuffer { } private DoubleArrayBuffer(int capacity, double[] backingArray, int arrayOffset, boolean isReadOnly) { - super(capacity); + super(capacity, 0); this.backingArray = backingArray; this.arrayOffset = arrayOffset; this.isReadOnly = isReadOnly; diff --git a/luni/src/main/java/java/nio/DoubleBuffer.java b/luni/src/main/java/java/nio/DoubleBuffer.java index 2fa74d2..f3062fb 100644 --- a/luni/src/main/java/java/nio/DoubleBuffer.java +++ b/luni/src/main/java/java/nio/DoubleBuffer.java @@ -81,7 +81,7 @@ public abstract class DoubleBuffer extends Buffer implements * the length, must not be negative and not greater than * {@code array.length - start}. * @return the created double buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code doubleCount} is invalid. */ public static DoubleBuffer wrap(double[] array, int start, int doubleCount) { @@ -92,8 +92,8 @@ public abstract class DoubleBuffer extends Buffer implements return buf; } - DoubleBuffer(int capacity) { - super(3, capacity, null); + DoubleBuffer(int capacity, long effectiveDirectAddress) { + super(3, capacity, effectiveDirectAddress); } public final double[] array() { @@ -127,7 +127,7 @@ public abstract class DoubleBuffer extends Buffer implements * limit is set to capacity; the mark is cleared. * * @return this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract DoubleBuffer compact(); @@ -141,7 +141,7 @@ public abstract class DoubleBuffer extends Buffer implements * @return a negative value if this is less than {@code other}; 0 if this * equals to {@code other}; a positive value if this is greater * than {@code other}. - * @exception ClassCastException + * @throws ClassCastException * if {@code other} is not a double buffer. */ public int compareTo(DoubleBuffer otherBuffer) { @@ -223,7 +223,7 @@ public abstract class DoubleBuffer extends Buffer implements * 1. * * @return the double at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is equal or greater than limit. */ public abstract double get(); @@ -238,7 +238,7 @@ public abstract class DoubleBuffer extends Buffer implements * @param dst * the destination double array. * @return this buffer. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code dst.length} is greater than {@code remaining()}. */ public DoubleBuffer get(double[] dst) { @@ -259,9 +259,9 @@ public abstract class DoubleBuffer extends Buffer implements * the number of doubles to read, must be no less than zero and * not greater than {@code dst.length - dstOffset}. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code dstOffset} or {@code doubleCount} is invalid. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code doubleCount} is greater than {@code remaining()}. */ public DoubleBuffer get(double[] dst, int dstOffset, int doubleCount) { @@ -281,7 +281,7 @@ public abstract class DoubleBuffer extends Buffer implements * @param index * the index, must not be negative and less than limit. * @return a double at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. */ public abstract double get(int index); @@ -360,9 +360,9 @@ public abstract class DoubleBuffer extends Buffer implements * @param d * the double to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is equal or greater than limit. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract DoubleBuffer put(double d); @@ -377,9 +377,9 @@ public abstract class DoubleBuffer extends Buffer implements * @param src * the source double array. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code src.length}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public final DoubleBuffer put(double[] src) { @@ -400,11 +400,11 @@ public abstract class DoubleBuffer extends Buffer implements * the number of doubles to write, must be no less than zero and * not greater than {@code src.length - srcOffset}. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code doubleCount}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code srcOffset} or {@code doubleCount} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public DoubleBuffer put(double[] src, int srcOffset, int doubleCount) { @@ -426,12 +426,12 @@ public abstract class DoubleBuffer extends Buffer implements * @param src * the source double buffer. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code src.remaining()} is greater than this buffer's * {@code remaining()}. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if {@code src} is this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public DoubleBuffer put(DoubleBuffer src) { @@ -459,9 +459,9 @@ public abstract class DoubleBuffer extends Buffer implements * @param d * the double to write. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract DoubleBuffer put(int index, double d); diff --git a/luni/src/main/java/java/nio/FileChannelImpl.java b/luni/src/main/java/java/nio/FileChannelImpl.java index a1be7fb..d72b9f0 100644 --- a/luni/src/main/java/java/nio/FileChannelImpl.java +++ b/luni/src/main/java/java/nio/FileChannelImpl.java @@ -17,12 +17,17 @@ package java.nio; +import android.system.ErrnoException; +import android.system.StructFlock; +import android.util.MutableLong; import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; +import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; +import java.nio.channels.FileLockInterruptionException; import java.nio.channels.NonReadableChannelException; import java.nio.channels.NonWritableChannelException; import java.nio.channels.OverlappingFileLockException; @@ -32,11 +37,8 @@ import java.util.Arrays; import java.util.Comparator; import java.util.SortedSet; import java.util.TreeSet; -import libcore.io.ErrnoException; import libcore.io.Libcore; -import libcore.io.StructFlock; -import libcore.util.MutableLong; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * Our concrete implementation of the abstract FileChannel class. @@ -50,7 +52,7 @@ final class FileChannelImpl extends FileChannel { } }; - private final Object stream; + private final Closeable ioObject; private final FileDescriptor fd; private final int mode; @@ -61,9 +63,9 @@ final class FileChannelImpl extends FileChannel { * Create a new file channel implementation class that wraps the given * fd and operates in the specified mode. */ - public FileChannelImpl(Object stream, FileDescriptor fd, int mode) { + public FileChannelImpl(Closeable ioObject, FileDescriptor fd, int mode) { this.fd = fd; - this.stream = stream; + this.ioObject = ioObject; this.mode = mode; } @@ -86,9 +88,7 @@ final class FileChannelImpl extends FileChannel { } protected void implCloseChannel() throws IOException { - if (stream instanceof Closeable) { - ((Closeable) stream).close(); - } + ioObject.close(); } private FileLock basicLock(long position, long size, boolean shared, boolean wait) throws IOException { @@ -166,7 +166,11 @@ final class FileChannelImpl extends FileChannel { resultLock = basicLock(position, size, shared, true); completed = true; } finally { - end(completed); + try { + end(completed); + } catch (ClosedByInterruptException e) { + throw new FileLockInterruptionException(); + } } } return resultLock; @@ -461,6 +465,9 @@ final class FileChannelImpl extends FileChannel { throw errnoException.rethrowAsIOException(); } } + if (position() > size) { + position(size); + } return this; } diff --git a/luni/src/main/java/java/nio/FloatArrayBuffer.java b/luni/src/main/java/java/nio/FloatArrayBuffer.java index 698174c..ac512a5 100644 --- a/luni/src/main/java/java/nio/FloatArrayBuffer.java +++ b/luni/src/main/java/java/nio/FloatArrayBuffer.java @@ -33,7 +33,7 @@ final class FloatArrayBuffer extends FloatBuffer { } private FloatArrayBuffer(int capacity, float[] backingArray, int arrayOffset, boolean isReadOnly) { - super(capacity); + super(capacity, 0); this.backingArray = backingArray; this.arrayOffset = arrayOffset; this.isReadOnly = isReadOnly; diff --git a/luni/src/main/java/java/nio/FloatBuffer.java b/luni/src/main/java/java/nio/FloatBuffer.java index cb7e55e..68514c5 100644 --- a/luni/src/main/java/java/nio/FloatBuffer.java +++ b/luni/src/main/java/java/nio/FloatBuffer.java @@ -80,9 +80,9 @@ public abstract class FloatBuffer extends Buffer implements * the length, must not be negative and not greater than * {@code array.length - start}. * @return the created float buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code floatCount} is invalid. - * @exception NullPointerException + * @throws NullPointerException * if {@code array} is null. */ public static FloatBuffer wrap(float[] array, int start, int floatCount) { @@ -93,8 +93,8 @@ public abstract class FloatBuffer extends Buffer implements return buf; } - FloatBuffer(int capacity) { - super(2, capacity, null); + FloatBuffer(int capacity, long effectiveDirectAddress) { + super(2, capacity, effectiveDirectAddress); } public final float[] array() { @@ -128,7 +128,7 @@ public abstract class FloatBuffer extends Buffer implements * limit is set to capacity; the mark is cleared. * * @return this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract FloatBuffer compact(); @@ -142,7 +142,7 @@ public abstract class FloatBuffer extends Buffer implements * @return a negative value if this is less than {@code otherBuffer}; 0 if * this equals to {@code otherBuffer}; a positive value if this is * greater than {@code otherBuffer}. - * @exception ClassCastException + * @throws ClassCastException * if {@code otherBuffer} is not a float buffer. */ public int compareTo(FloatBuffer otherBuffer) { @@ -224,7 +224,7 @@ public abstract class FloatBuffer extends Buffer implements * 1. * * @return the float at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is equal or greater than limit. */ public abstract float get(); @@ -239,7 +239,7 @@ public abstract class FloatBuffer extends Buffer implements * @param dst * the destination float array. * @return this buffer. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code dst.length} is greater than {@code remaining()}. */ public FloatBuffer get(float[] dst) { @@ -260,9 +260,9 @@ public abstract class FloatBuffer extends Buffer implements * the number of floats to read, must be no less than zero and no * greater than {@code dst.length - dstOffset}. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code dstOffset} or {@code floatCount} is invalid. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code floatCount} is greater than {@code remaining()}. */ public FloatBuffer get(float[] dst, int dstOffset, int floatCount) { @@ -282,7 +282,7 @@ public abstract class FloatBuffer extends Buffer implements * @param index * the index, must not be negative and less than limit. * @return a float at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. */ public abstract float get(int index); @@ -359,9 +359,9 @@ public abstract class FloatBuffer extends Buffer implements * @param f * the float to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is equal or greater than limit. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract FloatBuffer put(float f); @@ -376,9 +376,9 @@ public abstract class FloatBuffer extends Buffer implements * @param src * the source float array. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code src.length}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public final FloatBuffer put(float[] src) { @@ -399,11 +399,11 @@ public abstract class FloatBuffer extends Buffer implements * the number of floats to write, must be no less than zero and * no greater than {@code src.length - srcOffset}. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code floatCount}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code srcOffset} or {@code floatCount} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public FloatBuffer put(float[] src, int srcOffset, int floatCount) { @@ -425,12 +425,12 @@ public abstract class FloatBuffer extends Buffer implements * @param src * the source float buffer. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code src.remaining()} is greater than this buffer's * {@code remaining()}. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if {@code src} is this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public FloatBuffer put(FloatBuffer src) { @@ -458,9 +458,9 @@ public abstract class FloatBuffer extends Buffer implements * @param f * the float to write. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract FloatBuffer put(int index, float f); diff --git a/luni/src/main/java/java/nio/IntArrayBuffer.java b/luni/src/main/java/java/nio/IntArrayBuffer.java index a5f9f39..423b1af 100644 --- a/luni/src/main/java/java/nio/IntArrayBuffer.java +++ b/luni/src/main/java/java/nio/IntArrayBuffer.java @@ -33,7 +33,7 @@ final class IntArrayBuffer extends IntBuffer { } private IntArrayBuffer(int capacity, int[] backingArray, int arrayOffset, boolean isReadOnly) { - super(capacity); + super(capacity, 0); this.backingArray = backingArray; this.arrayOffset = arrayOffset; this.isReadOnly = isReadOnly; diff --git a/luni/src/main/java/java/nio/IntBuffer.java b/luni/src/main/java/java/nio/IntBuffer.java index a20f6c2..50e95b0 100644 --- a/luni/src/main/java/java/nio/IntBuffer.java +++ b/luni/src/main/java/java/nio/IntBuffer.java @@ -78,7 +78,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * the length, must not be negative and not greater than * {@code array.length - start}. * @return the created int buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code intCount} is invalid. */ public static IntBuffer wrap(int[] array, int start, int intCount) { @@ -89,8 +89,8 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> return buf; } - IntBuffer(int capacity) { - super(2, capacity, null); + IntBuffer(int capacity, long effectiveDirectAddress) { + super(2, capacity, effectiveDirectAddress); } public final int[] array() { @@ -124,7 +124,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * limit is set to capacity; the mark is cleared. * * @return this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract IntBuffer compact(); @@ -138,7 +138,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * @return a negative value if this is less than {@code other}; 0 if this * equals to {@code other}; a positive value if this is greater * than {@code other}. - * @exception ClassCastException + * @throws ClassCastException * if {@code other} is not an int buffer. */ public int compareTo(IntBuffer otherBuffer) { @@ -210,7 +210,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * Returns the int at the current position and increases the position by 1. * * @return the int at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is equal or greater than limit. */ public abstract int get(); @@ -225,7 +225,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * @param dst * the destination int array. * @return this buffer. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code dst.length} is greater than {@code remaining()}. */ public IntBuffer get(int[] dst) { @@ -246,9 +246,9 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * the number of ints to read, must be no less than zero and not * greater than {@code dst.length - dstOffset}. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code dstOffset} or {@code intCount} is invalid. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code intCount} is greater than {@code remaining()}. */ public IntBuffer get(int[] dst, int dstOffset, int intCount) { @@ -268,7 +268,7 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * @param index * the index, must not be negative and less than limit. * @return an int at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. */ public abstract int get(int index); @@ -345,9 +345,9 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * @param i * the int to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is equal or greater than limit. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract IntBuffer put(int i); @@ -362,9 +362,9 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * @param src * the source int array. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code src.length}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public final IntBuffer put(int[] src) { @@ -385,11 +385,11 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * the number of ints to write, must be no less than zero and not * greater than {@code src.length - srcOffset}. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code intCount}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code srcOffset} or {@code intCount} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public IntBuffer put(int[] src, int srcOffset, int intCount) { @@ -414,12 +414,12 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * @param src * the source int buffer. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code src.remaining()} is greater than this buffer's * {@code remaining()}. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if {@code src} is this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public IntBuffer put(IntBuffer src) { @@ -447,9 +447,9 @@ public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer> * @param i * the int to write. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract IntBuffer put(int index, int i); diff --git a/luni/src/main/java/java/nio/IoVec.java b/luni/src/main/java/java/nio/IoVec.java index f14f4f2..e20709c 100644 --- a/luni/src/main/java/java/nio/IoVec.java +++ b/luni/src/main/java/java/nio/IoVec.java @@ -16,10 +16,10 @@ package java.nio; +import android.system.ErrnoException; import java.io.FileDescriptor; import java.io.IOException; import libcore.io.Libcore; -import libcore.io.ErrnoException; /** * Used to implement java.nio read(ByteBuffer[])/write(ByteBuffer[]) operations as POSIX readv(2) diff --git a/luni/src/main/java/java/nio/LongArrayBuffer.java b/luni/src/main/java/java/nio/LongArrayBuffer.java index 6419d73..f03a91e 100644 --- a/luni/src/main/java/java/nio/LongArrayBuffer.java +++ b/luni/src/main/java/java/nio/LongArrayBuffer.java @@ -33,7 +33,7 @@ final class LongArrayBuffer extends LongBuffer { } private LongArrayBuffer(int capacity, long[] backingArray, int arrayOffset, boolean isReadOnly) { - super(capacity); + super(capacity, 0); this.backingArray = backingArray; this.arrayOffset = arrayOffset; this.isReadOnly = isReadOnly; diff --git a/luni/src/main/java/java/nio/LongBuffer.java b/luni/src/main/java/java/nio/LongBuffer.java index 55adab6..09f8418 100644 --- a/luni/src/main/java/java/nio/LongBuffer.java +++ b/luni/src/main/java/java/nio/LongBuffer.java @@ -80,7 +80,7 @@ public abstract class LongBuffer extends Buffer implements * the length, must not be negative and not greater than * {@code array.length - start}. * @return the created long buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code longCount} is invalid. */ public static LongBuffer wrap(long[] array, int start, int longCount) { @@ -91,8 +91,8 @@ public abstract class LongBuffer extends Buffer implements return buf; } - LongBuffer(int capacity) { - super(3, capacity, null); + LongBuffer(int capacity, long effectiveDirectAddress) { + super(3, capacity, effectiveDirectAddress); } public final long[] array() { @@ -126,7 +126,7 @@ public abstract class LongBuffer extends Buffer implements * limit is set to capacity; the mark is cleared. * * @return this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract LongBuffer compact(); @@ -140,7 +140,7 @@ public abstract class LongBuffer extends Buffer implements * @return a negative value if this is less than {@code otherBuffer}; 0 if * this equals to {@code otherBuffer}; a positive value if this is * greater than {@code otherBuffer} - * @exception ClassCastException + * @throws ClassCastException * if {@code otherBuffer} is not a long buffer. */ public int compareTo(LongBuffer otherBuffer) { @@ -212,7 +212,7 @@ public abstract class LongBuffer extends Buffer implements * Returns the long at the current position and increase the position by 1. * * @return the long at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is equal or greater than limit. */ public abstract long get(); @@ -227,7 +227,7 @@ public abstract class LongBuffer extends Buffer implements * @param dst * the destination long array. * @return this buffer. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code dst.length} is greater than {@code remaining()}. */ public LongBuffer get(long[] dst) { @@ -248,9 +248,9 @@ public abstract class LongBuffer extends Buffer implements * the number of longs to read, must be no less than zero and not * greater than {@code dst.length - dstOffset}. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code dstOffset} or {@code longCount} is invalid. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code longCount} is greater than {@code remaining()}. */ public LongBuffer get(long[] dst, int dstOffset, int longCount) { @@ -270,7 +270,7 @@ public abstract class LongBuffer extends Buffer implements * @param index * the index, must not be negative and less than limit. * @return the long at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. */ public abstract long get(int index); @@ -349,9 +349,9 @@ public abstract class LongBuffer extends Buffer implements * @param l * the long to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is equal or greater than limit. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract LongBuffer put(long l); @@ -366,9 +366,9 @@ public abstract class LongBuffer extends Buffer implements * @param src * the source long array. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code src.length}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public final LongBuffer put(long[] src) { @@ -389,11 +389,11 @@ public abstract class LongBuffer extends Buffer implements * the number of longs to write, must be no less than zero and * not greater than {@code src.length - srcOffset}. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code longCount}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code srcOffset} or {@code longCount} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public LongBuffer put(long[] src, int srcOffset, int longCount) { @@ -415,12 +415,12 @@ public abstract class LongBuffer extends Buffer implements * @param src * the source long buffer. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code src.remaining()} is greater than this buffer's * {@code remaining()}. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if {@code src} is this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public LongBuffer put(LongBuffer src) { @@ -448,9 +448,9 @@ public abstract class LongBuffer extends Buffer implements * @param l * the long to write. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract LongBuffer put(int index, long l); diff --git a/luni/src/main/java/java/nio/MappedByteBuffer.java b/luni/src/main/java/java/nio/MappedByteBuffer.java index 2d44d0f..ce19c0c 100644 --- a/luni/src/main/java/java/nio/MappedByteBuffer.java +++ b/luni/src/main/java/java/nio/MappedByteBuffer.java @@ -16,11 +16,11 @@ package java.nio; +import android.system.ErrnoException; import java.nio.channels.FileChannel.MapMode; -import libcore.io.ErrnoException; import libcore.io.Libcore; -import libcore.io.Memory; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.MS_SYNC; +import static android.system.OsConstants._SC_PAGE_SIZE; /** * {@code MappedByteBuffer} is a special kind of direct byte buffer which maps a @@ -38,10 +38,12 @@ import static libcore.io.OsConstants.*; */ public abstract class MappedByteBuffer extends ByteBuffer { final MapMode mapMode; + final MemoryBlock block; - MappedByteBuffer(MemoryBlock block, int capacity, MapMode mapMode) { - super(capacity, block); + MappedByteBuffer(MemoryBlock block, int capacity, MapMode mapMode, long effectiveDirectAddress) { + super(capacity, effectiveDirectAddress); this.mapMode = mapMode; + this.block = block; } /** diff --git a/luni/src/main/java/java/nio/MemoryBlock.java b/luni/src/main/java/java/nio/MemoryBlock.java index 718b020..b62e3c6 100644 --- a/luni/src/main/java/java/nio/MemoryBlock.java +++ b/luni/src/main/java/java/nio/MemoryBlock.java @@ -17,14 +17,18 @@ package java.nio; +import android.system.ErrnoException; import dalvik.system.VMRuntime; import java.io.FileDescriptor; import java.io.IOException; import java.nio.channels.FileChannel.MapMode; -import libcore.io.ErrnoException; import libcore.io.Libcore; import libcore.io.Memory; -import static libcore.io.OsConstants.*; + +import static android.system.OsConstants.MAP_PRIVATE; +import static android.system.OsConstants.MAP_SHARED; +import static android.system.OsConstants.PROT_READ; +import static android.system.OsConstants.PROT_WRITE; class MemoryBlock { /** @@ -44,8 +48,8 @@ class MemoryBlock { // a state where munmap(2) could return an error. throw new AssertionError(errnoException); } - address = 0; } + super.free(); } @Override protected void finalize() throws Throwable { @@ -74,7 +78,7 @@ class MemoryBlock { @Override public void free() { array = null; - address = 0; + super.free(); } } @@ -90,6 +94,8 @@ class MemoryBlock { protected long address; protected final long size; + private boolean accessible; + private boolean freed; public static MemoryBlock mmap(FileDescriptor fd, long offset, long size, MapMode mapMode) throws IOException { if (size == 0) { @@ -134,6 +140,8 @@ class MemoryBlock { private MemoryBlock(long address, long size) { this.address = address; this.size = size; + accessible = true; + freed = false; } // Used to support array/arrayOffset/hasArray for direct buffers. @@ -142,6 +150,20 @@ class MemoryBlock { } public void free() { + address = 0; + freed = true; + } + + public boolean isFreed() { + return freed; + } + + public boolean isAccessible() { + return !isFreed() && accessible; + } + + public final void setAccessible(boolean accessible) { + this.accessible = accessible; } public final void pokeByte(int offset, byte value) { diff --git a/luni/src/main/java/java/nio/NIOAccess.java b/luni/src/main/java/java/nio/NIOAccess.java index 12af44d..ddb102e 100644 --- a/luni/src/main/java/java/nio/NIOAccess.java +++ b/luni/src/main/java/java/nio/NIOAccess.java @@ -24,17 +24,11 @@ final class NIOAccess { /** * Returns the underlying native pointer to the data of the given * Buffer starting at the Buffer's current position, or 0 if the - * Buffer is not backed by native heap storage. Note that this is - * different than what the Harmony implementation calls a "base - * address." - * - * @param b the Buffer to be queried - * @return the native pointer to the Buffer's data at its current - * position, or 0 if there is none + * Buffer is not backed by native heap storage. */ static long getBasePointer(Buffer b) { long address = b.effectiveDirectAddress; - if (address == 0) { + if (address == 0L) { return 0L; } return address + (b.position << b._elementSizeShift); @@ -43,10 +37,6 @@ final class NIOAccess { /** * Returns the underlying Java array containing the data of the * given Buffer, or null if the Buffer is not backed by a Java array. - * - * @param b the Buffer to be queried - * @return the Java array containing the Buffer's data, or null if - * there is none */ static Object getBaseArray(Buffer b) { return b.hasArray() ? b.array() : null; @@ -58,9 +48,6 @@ final class NIOAccess { * the actual start of the data. The start of the data takes into * account the Buffer's current position. This method is only * meaningful if getBaseArray() returns non-null. - * - * @param b the Buffer to be queried - * @return the data offset in bytes to the start of this Buffer's data */ static int getBaseArrayOffset(Buffer b) { return b.hasArray() ? ((b.arrayOffset() + b.position) << b._elementSizeShift) : 0; diff --git a/luni/src/main/java/java/nio/NioUtils.java b/luni/src/main/java/java/nio/NioUtils.java index a1a46b6..f2a0b10 100644 --- a/luni/src/main/java/java/nio/NioUtils.java +++ b/luni/src/main/java/java/nio/NioUtils.java @@ -16,8 +16,12 @@ package java.nio; +import java.io.Closeable; import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; +import java.util.Set; /** * @hide internal use only @@ -43,8 +47,8 @@ public final class NioUtils { /** * Helps bridge between io and nio. */ - public static FileChannel newFileChannel(Object stream, FileDescriptor fd, int mode) { - return new FileChannelImpl(stream, fd, mode); + public static FileChannel newFileChannel(Closeable ioObject, FileDescriptor fd, int mode) { + return new FileChannelImpl(ioObject, fd, mode); } /** diff --git a/luni/src/main/java/java/nio/PipeImpl.java b/luni/src/main/java/java/nio/PipeImpl.java index d4dde3b..ebc8a91 100644 --- a/luni/src/main/java/java/nio/PipeImpl.java +++ b/luni/src/main/java/java/nio/PipeImpl.java @@ -16,16 +16,16 @@ package java.nio; +import android.system.ErrnoException; import java.io.Closeable; import java.io.FileDescriptor; import java.io.IOException; import java.nio.channels.Pipe; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; -import libcore.io.ErrnoException; import libcore.io.IoUtils; import libcore.io.Libcore; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /* * Implements {@link java.nio.channels.Pipe}. diff --git a/luni/src/main/java/java/nio/SelectorImpl.java b/luni/src/main/java/java/nio/SelectorImpl.java index d63fa63..45406b1 100644 --- a/luni/src/main/java/java/nio/SelectorImpl.java +++ b/luni/src/main/java/java/nio/SelectorImpl.java @@ -15,33 +15,38 @@ */ package java.nio; +import android.system.ErrnoException; +import android.system.StructPollfd; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.io.IOException; import java.nio.channels.ClosedSelectorException; import java.nio.channels.IllegalSelectorException; -import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; -import static java.nio.channels.SelectionKey.*; import java.nio.channels.Selector; -import java.nio.channels.SocketChannel; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.AbstractSelectionKey; import java.nio.channels.spi.AbstractSelector; import java.nio.channels.spi.SelectorProvider; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.UnsafeArrayList; -import libcore.io.ErrnoException; import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; -import libcore.io.StructPollfd; -import libcore.util.EmptyArray; -import static libcore.io.OsConstants.*; + +import static android.system.OsConstants.EINTR; +import static android.system.OsConstants.POLLERR; +import static android.system.OsConstants.POLLHUP; +import static android.system.OsConstants.POLLIN; +import static android.system.OsConstants.POLLOUT; +import static java.nio.channels.SelectionKey.OP_ACCEPT; +import static java.nio.channels.SelectionKey.OP_CONNECT; +import static java.nio.channels.SelectionKey.OP_READ; +import static java.nio.channels.SelectionKey.OP_WRITE; /* * Default implementation of java.nio.channels.Selector @@ -255,7 +260,7 @@ final class SelectorImpl extends AbstractSelector { int ops = key.interestOpsNoCheck(); int selectedOps = 0; - if ((pollFd.revents & POLLHUP) != 0) { + if ((pollFd.revents & POLLHUP) != 0 || (pollFd.revents & POLLERR) != 0) { // If there was an error condition, we definitely want to wake listeners, // regardless of what they're waiting for. Failure is always interesting. selectedOps |= ops; @@ -321,6 +326,7 @@ final class SelectorImpl extends AbstractSelector { try { Libcore.os.write(wakeupOut, new byte[] { 1 }, 0, 1); } catch (ErrnoException ignored) { + } catch (InterruptedIOException ignored) { } return this; } diff --git a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java index 3fb61a3..ae33672 100644 --- a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java +++ b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java @@ -17,13 +17,13 @@ package java.nio; +import android.system.ErrnoException; import java.io.FileDescriptor; import java.io.IOException; -import java.net.PlainServerSocketImpl; +import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; -import java.net.SocketImpl; import java.net.SocketTimeoutException; import java.nio.channels.ClosedChannelException; import java.nio.channels.IllegalBlockingModeException; @@ -31,9 +31,11 @@ import java.nio.channels.NotYetBoundException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; -import libcore.io.ErrnoException; +import java.nio.channels.UnresolvedAddressException; +import java.nio.channels.UnsupportedAddressTypeException; +import java.util.Set; import libcore.io.IoUtils; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * The default ServerSocketChannel. @@ -41,31 +43,28 @@ import static libcore.io.OsConstants.*; final class ServerSocketChannelImpl extends ServerSocketChannel implements FileDescriptorChannel { private final ServerSocketAdapter socket; - private final SocketImpl impl; - - private boolean isBound = false; private final Object acceptLock = new Object(); public ServerSocketChannelImpl(SelectorProvider sp) throws IOException { super(sp); this.socket = new ServerSocketAdapter(this); - this.impl = socket.getImpl$(); } @Override public ServerSocket socket() { return socket; } - @Override public SocketChannel accept() throws IOException { + @Override + public SocketChannel accept() throws IOException { if (!isOpen()) { throw new ClosedChannelException(); } - if (!isBound) { + if (!socket.isBound()) { throw new NotYetBoundException(); } - // Create an empty socket channel. This will be populated by ServerSocketAdapter.accept. + // Create an empty socket channel. This will be populated by ServerSocketAdapter.implAccept. SocketChannelImpl result = new SocketChannelImpl(provider(), false); try { begin(); @@ -81,9 +80,9 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD } } } finally { - end(result.socket().isConnected()); + end(result.isConnected()); } - return result.socket().isConnected() ? result : null; + return result.isConnected() ? result : null; } private boolean shouldThrowSocketTimeoutExceptionFromAccept(SocketTimeoutException e) { @@ -100,17 +99,19 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD } @Override protected void implConfigureBlocking(boolean blocking) throws IOException { - IoUtils.setBlocking(impl.getFD$(), blocking); + IoUtils.setBlocking(socket.getFD$(), blocking); } + @Override synchronized protected void implCloseSelectableChannel() throws IOException { if (!socket.isClosed()) { socket.close(); } } + @Override public FileDescriptor getFD() { - return impl.getFD$(); + return socket.getFD$(); } private static class ServerSocketAdapter extends ServerSocket { @@ -120,13 +121,8 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD this.channelImpl = aChannelImpl; } - @Override public void bind(SocketAddress localAddress, int backlog) throws IOException { - super.bind(localAddress, backlog); - channelImpl.isBound = true; - } - @Override public Socket accept() throws IOException { - if (!channelImpl.isBound) { + if (!isBound()) { throw new IllegalBlockingModeException(); } SocketChannel sc = channelImpl.accept(); @@ -142,9 +138,12 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD try { synchronized (this) { super.implAccept(clientSocket); - clientSocketChannel.setConnected(); - clientSocketChannel.setBound(true); - clientSocketChannel.finishAccept(); + + // Sync the client socket's associated channel state with the Socket and OS. + InetSocketAddress remoteAddress = + new InetSocketAddress( + clientSocket.getInetAddress(), clientSocket.getPort()); + clientSocketChannel.onAccept(remoteAddress, false /* updateSocketState */); } connectOK = true; } finally { @@ -159,23 +158,17 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD return channelImpl; } - @Override public boolean isBound() { - return channelImpl.isBound; - } - - @Override public void bind(SocketAddress localAddress) throws IOException { - super.bind(localAddress); - channelImpl.isBound = true; - } - @Override public void close() throws IOException { synchronized (channelImpl) { + super.close(); if (channelImpl.isOpen()) { channelImpl.close(); - } else { - super.close(); } } } + + private FileDescriptor getFD$() { + return super.getImpl$().getFD$(); + } } } diff --git a/luni/src/main/java/java/nio/ShortArrayBuffer.java b/luni/src/main/java/java/nio/ShortArrayBuffer.java index a092cb0..3c41174 100644 --- a/luni/src/main/java/java/nio/ShortArrayBuffer.java +++ b/luni/src/main/java/java/nio/ShortArrayBuffer.java @@ -33,7 +33,7 @@ final class ShortArrayBuffer extends ShortBuffer { } private ShortArrayBuffer(int capacity, short[] backingArray, int arrayOffset, boolean isReadOnly) { - super(capacity); + super(capacity, 0); this.backingArray = backingArray; this.arrayOffset = arrayOffset; this.isReadOnly = isReadOnly; diff --git a/luni/src/main/java/java/nio/ShortBuffer.java b/luni/src/main/java/java/nio/ShortBuffer.java index 8da01ed..cbc5a47 100644 --- a/luni/src/main/java/java/nio/ShortBuffer.java +++ b/luni/src/main/java/java/nio/ShortBuffer.java @@ -80,7 +80,7 @@ public abstract class ShortBuffer extends Buffer implements * the length, must not be negative and not greater than * {@code array.length - start}. * @return the created short buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code start} or {@code shortCount} is invalid. */ public static ShortBuffer wrap(short[] array, int start, int shortCount) { @@ -91,8 +91,8 @@ public abstract class ShortBuffer extends Buffer implements return buf; } - ShortBuffer(int capacity) { - super(1, capacity, null); + ShortBuffer(int capacity, long effectiveDirectAddress) { + super(1, capacity, effectiveDirectAddress); } public final short[] array() { @@ -126,7 +126,7 @@ public abstract class ShortBuffer extends Buffer implements * limit is set to capacity; the mark is cleared. * * @return this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ShortBuffer compact(); @@ -140,7 +140,7 @@ public abstract class ShortBuffer extends Buffer implements * @return a negative value if this is less than {@code otherBuffer}; 0 if * this equals to {@code otherBuffer}; a positive value if this is * greater than {@code otherBuffer}. - * @exception ClassCastException + * @throws ClassCastException * if {@code otherBuffer} is not a short buffer. */ public int compareTo(ShortBuffer otherBuffer) { @@ -213,7 +213,7 @@ public abstract class ShortBuffer extends Buffer implements * 1. * * @return the short at the current position. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if the position is equal or greater than limit. */ public abstract short get(); @@ -228,7 +228,7 @@ public abstract class ShortBuffer extends Buffer implements * @param dst * the destination short array. * @return this buffer. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code dst.length} is greater than {@code remaining()}. */ public ShortBuffer get(short[] dst) { @@ -249,9 +249,9 @@ public abstract class ShortBuffer extends Buffer implements * the number of shorts to read, must be no less than zero and * not greater than {@code dst.length - dstOffset}. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code dstOffset} or {@code shortCount} is invalid. - * @exception BufferUnderflowException + * @throws BufferUnderflowException * if {@code shortCount} is greater than {@code remaining()}. */ public ShortBuffer get(short[] dst, int dstOffset, int shortCount) { @@ -271,7 +271,7 @@ public abstract class ShortBuffer extends Buffer implements * @param index * the index, must not be negative and less than limit. * @return a short at the specified index. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. */ public abstract short get(int index); @@ -348,9 +348,9 @@ public abstract class ShortBuffer extends Buffer implements * @param s * the short to write. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if position is equal or greater than limit. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ShortBuffer put(short s); @@ -365,9 +365,9 @@ public abstract class ShortBuffer extends Buffer implements * @param src * the source short array. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code src.length}. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public final ShortBuffer put(short[] src) { @@ -388,11 +388,11 @@ public abstract class ShortBuffer extends Buffer implements * the number of shorts to write, must be no less than zero and * not greater than {@code src.length - srcOffset}. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code remaining()} is less than {@code shortCount}. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if either {@code srcOffset} or {@code shortCount} is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public ShortBuffer put(short[] src, int srcOffset, int shortCount) { @@ -414,12 +414,12 @@ public abstract class ShortBuffer extends Buffer implements * @param src * the source short buffer. * @return this buffer. - * @exception BufferOverflowException + * @throws BufferOverflowException * if {@code src.remaining()} is greater than this buffer's * {@code remaining()}. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * if {@code src} is this buffer. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public ShortBuffer put(ShortBuffer src) { @@ -447,9 +447,9 @@ public abstract class ShortBuffer extends Buffer implements * @param s * the short to write. * @return this buffer. - * @exception IndexOutOfBoundsException + * @throws IndexOutOfBoundsException * if index is invalid. - * @exception ReadOnlyBufferException + * @throws ReadOnlyBufferException * if no changes may be made to the contents of this buffer. */ public abstract ShortBuffer put(int index, short s); diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java index 714005f..d5cb716 100644 --- a/luni/src/main/java/java/nio/SocketChannelImpl.java +++ b/luni/src/main/java/java/nio/SocketChannelImpl.java @@ -17,9 +17,12 @@ package java.nio; +import android.system.ErrnoException; import java.io.FileDescriptor; -import java.io.IOException; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; import java.io.InputStream; +import java.io.IOException; import java.io.OutputStream; import java.net.ConnectException; import java.net.Inet4Address; @@ -30,7 +33,6 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketUtils; -import java.net.UnknownHostException; import java.nio.channels.AlreadyConnectedException; import java.nio.channels.ClosedChannelException; import java.nio.channels.ConnectionPendingException; @@ -38,15 +40,15 @@ import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.NoConnectionPendingException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; import java.nio.channels.UnresolvedAddressException; import java.nio.channels.UnsupportedAddressTypeException; -import java.nio.channels.spi.SelectorProvider; import java.util.Arrays; -import libcore.io.ErrnoException; -import libcore.io.Libcore; +import java.util.Set; import libcore.io.IoBridge; import libcore.io.IoUtils; -import static libcore.io.OsConstants.*; +import libcore.io.Libcore; +import static android.system.OsConstants.*; /* * The default implementation class of java.nio.channels.SocketChannel. @@ -74,6 +76,7 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { // The address to be connected. private InetSocketAddress connectAddress = null; + // The local address the socket is bound to. private InetAddress localAddress = null; private int localPort; @@ -133,20 +136,34 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { return socket; } - @Override - synchronized public boolean isConnected() { - return status == SOCKET_STATUS_CONNECTED; - } - - /* - * Status setting used by other class. + /** + * Initialise the isBound, localAddress and localPort state from the file descriptor. Used when + * some or all of the bound state has been left to the OS to decide, or when the Socket handled + * bind() or connect(). + * + * @param updateSocketState + * if the associated socket (if present) needs to be updated + * @hide package visible for other nio classes */ - synchronized void setConnected() { - status = SOCKET_STATUS_CONNECTED; + void onBind(boolean updateSocketState) { + SocketAddress sa; + try { + sa = Libcore.os.getsockname(fd); + } catch (ErrnoException errnoException) { + throw new AssertionError(errnoException); + } + isBound = true; + InetSocketAddress localSocketAddress = (InetSocketAddress) sa; + localAddress = localSocketAddress.getAddress(); + localPort = localSocketAddress.getPort(); + if (updateSocketState && socket != null) { + socket.onBind(localAddress, localPort); + } } - void setBound(boolean flag) { - isBound = flag; + @Override + synchronized public boolean isConnected() { + return status == SOCKET_STATUS_CONNECTED; } @Override @@ -169,16 +186,22 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { normalAddr = InetAddress.getLocalHost(); } + boolean isBlocking = isBlocking(); boolean finished = false; + int newStatus; try { - if (isBlocking()) { + if (isBlocking) { begin(); } - finished = IoBridge.connect(fd, normalAddr, port); - isBound = finished; + // When in blocking mode, IoBridge.connect() will return without an exception when the + // socket is connected. When in non-blocking mode it will return without an exception + // without knowing the result of the connection attempt, which could still be going on. + IoBridge.connect(fd, normalAddr, port); + newStatus = isBlocking ? SOCKET_STATUS_CONNECTED : SOCKET_STATUS_PENDING; + finished = true; } catch (IOException e) { if (isEINPROGRESS(e)) { - status = SOCKET_STATUS_PENDING; + newStatus = SOCKET_STATUS_PENDING; } else { if (isOpen()) { close(); @@ -187,26 +210,36 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { throw e; } } finally { - if (isBlocking()) { + if (isBlocking) { end(finished); } } - initLocalAddressAndPort(); - connectAddress = inetSocketAddress; - if (socket != null) { - socket.socketImpl().initRemoteAddressAndPort(connectAddress.getAddress(), - connectAddress.getPort()); + // If the channel was not bound, a connection attempt will have caused an implicit bind() to + // take place. Keep the local address state held by the channel and the socket up to date. + if (!isBound) { + onBind(true /* updateSocketState */); } - synchronized (this) { - if (isBlocking()) { - status = (finished ? SOCKET_STATUS_CONNECTED : SOCKET_STATUS_UNCONNECTED); - } else { - status = SOCKET_STATUS_PENDING; - } + // Keep the connected state held by the channel and the socket up to date. + onConnectStatusChanged(inetSocketAddress, newStatus, true /* updateSocketState */); + + return status == SOCKET_STATUS_CONNECTED; + } + + /** + * Initialise the connect() state with the supplied information. + * + * @param updateSocketState + * if the associated socket (if present) needs to be updated + * @hide package visible for other nio classes + */ + void onConnectStatusChanged(InetSocketAddress address, int status, boolean updateSocketState) { + this.status = status; + connectAddress = address; + if (status == SOCKET_STATUS_CONNECTED && updateSocketState && socket != null) { + socket.onConnect(connectAddress.getAddress(), connectAddress.getPort()); } - return finished; } private boolean isEINPROGRESS(IOException e) { @@ -222,21 +255,6 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { return false; } - private void initLocalAddressAndPort() { - SocketAddress sa; - try { - sa = Libcore.os.getsockname(fd); - } catch (ErrnoException errnoException) { - throw new AssertionError(errnoException); - } - InetSocketAddress isa = (InetSocketAddress) sa; - localAddress = isa.getAddress(); - localPort = isa.getPort(); - if (socket != null) { - socket.socketImpl().initLocalPort(localPort); - } - } - @Override public boolean finishConnect() throws IOException { synchronized (this) { @@ -257,7 +275,6 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { InetAddress inetAddress = connectAddress.getAddress(); int port = connectAddress.getPort(); finished = IoBridge.isConnected(fd, inetAddress, port, 0, 0); // Return immediately. - isBound = finished; } catch (ConnectException e) { if (isOpen()) { close(); @@ -270,15 +287,13 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { synchronized (this) { status = (finished ? SOCKET_STATUS_CONNECTED : status); - isBound = finished; + if (finished && socket != null) { + socket.onConnect(connectAddress.getAddress(), connectAddress.getPort()); + } } return finished; } - void finishAccept() { - initLocalAddressAndPort(); - } - @Override public int read(ByteBuffer dst) throws IOException { dst.checkWritable(); @@ -447,23 +462,17 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { } /* - * Get local address. - */ - public InetAddress getLocalAddress() throws UnknownHostException { - return isBound ? localAddress : Inet4Address.ANY; - } - - /* * Do really closing action here. */ @Override protected synchronized void implCloseSelectableChannel() throws IOException { if (status != SOCKET_STATUS_CLOSED) { status = SOCKET_STATUS_CLOSED; + // IoBridge.closeAndSignalBlockedThreads(fd) is idempotent: It is safe to call on an + // already-closed file descriptor. + IoBridge.closeAndSignalBlockedThreads(fd); if (socket != null && !socket.isClosed()) { - socket.close(); - } else { - IoBridge.closeSocket(fd); + socket.onClose(); } } } @@ -479,6 +488,12 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { return fd; } + /* @hide used by ServerSocketChannelImpl to sync channel state during accept() */ + public void onAccept(InetSocketAddress remoteAddress, boolean updateSocketState) { + onBind(updateSocketState); + onConnectStatusChanged(remoteAddress, SOCKET_STATUS_CONNECTED, updateSocketState); + } + /* * Adapter classes for internal socket. */ @@ -486,15 +501,24 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { private final SocketChannelImpl channel; private final PlainSocketImpl socketImpl; - SocketAdapter(PlainSocketImpl socketImpl, SocketChannelImpl channel) throws SocketException { + SocketAdapter(PlainSocketImpl socketImpl, SocketChannelImpl channel) + throws SocketException { super(socketImpl); this.socketImpl = socketImpl; this.channel = channel; SocketUtils.setCreated(this); - } - PlainSocketImpl socketImpl() { - return socketImpl; + // Sync state socket state with the channel it is being created from + if (channel.isBound) { + onBind(channel.localAddress, channel.localPort); + } + if (channel.isConnected()) { + onConnect(channel.connectAddress.getAddress(), channel.connectAddress.getPort()); + } + if (!channel.isOpen()) { + onClose(); + } + } @Override @@ -503,25 +527,6 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { } @Override - public boolean isBound() { - return channel.isBound; - } - - @Override - public boolean isConnected() { - return channel.isConnected(); - } - - @Override - public InetAddress getLocalAddress() { - try { - return channel.getLocalAddress(); - } catch (UnknownHostException e) { - return null; - } - } - - @Override public void connect(SocketAddress remoteAddr, int timeout) throws IOException { if (!channel.isBlocking()) { throw new IllegalBlockingModeException(); @@ -530,10 +535,11 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { throw new AlreadyConnectedException(); } super.connect(remoteAddr, timeout); - channel.initLocalAddressAndPort(); + channel.onBind(false); if (super.isConnected()) { - channel.setConnected(); - channel.isBound = super.isBound(); + InetSocketAddress remoteInetAddress = (InetSocketAddress) remoteAddr; + channel.onConnectStatusChanged( + remoteInetAddress, SOCKET_STATUS_CONNECTED, false /* updateSocketState */); } } @@ -546,47 +552,29 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { throw new ConnectionPendingException(); } super.bind(localAddr); - channel.initLocalAddressAndPort(); - channel.isBound = true; + channel.onBind(false); } @Override public void close() throws IOException { synchronized (channel) { + super.close(); if (channel.isOpen()) { + // channel.close() recognizes the socket is closed and avoids recursion. There + // is no channel.onClose() because the "closed" field is private. channel.close(); - } else { - super.close(); } - channel.status = SocketChannelImpl.SOCKET_STATUS_CLOSED; } } @Override public OutputStream getOutputStream() throws IOException { - checkOpenAndConnected(); - if (isOutputShutdown()) { - throw new SocketException("Socket output is shutdown"); - } - return new SocketChannelOutputStream(channel); + return new BlockingCheckOutputStream(super.getOutputStream(), channel); } @Override public InputStream getInputStream() throws IOException { - checkOpenAndConnected(); - if (isInputShutdown()) { - throw new SocketException("Socket input is shutdown"); - } - return new SocketChannelInputStream(channel); - } - - private void checkOpenAndConnected() throws SocketException { - if (!channel.isOpen()) { - throw new SocketException("Socket is closed"); - } - if (!channel.isConnected()) { - throw new SocketException("Socket is not connected"); - } + return new BlockingCheckInputStream(super.getInputStream(), channel); } @Override @@ -596,86 +584,92 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { } /* - * This output stream delegates all operations to the associated channel. * Throws an IllegalBlockingModeException if the channel is in non-blocking * mode when performing write operations. */ - private static class SocketChannelOutputStream extends OutputStream { + private static class BlockingCheckOutputStream extends FilterOutputStream { private final SocketChannel channel; - public SocketChannelOutputStream(SocketChannel channel) { + public BlockingCheckOutputStream(OutputStream out, SocketChannel channel) { + super(out); this.channel = channel; } - /* - * Closes this stream and channel. - * - * @exception IOException thrown if an error occurs during the close - */ @Override - public void close() throws IOException { - channel.close(); + public void write(byte[] buffer, int offset, int byteCount) throws IOException { + checkBlocking(); + out.write(buffer, offset, byteCount); } @Override - public void write(byte[] buffer, int offset, int byteCount) throws IOException { - Arrays.checkOffsetAndCount(buffer.length, offset, byteCount); - ByteBuffer buf = ByteBuffer.wrap(buffer, offset, byteCount); - if (!channel.isBlocking()) { - throw new IllegalBlockingModeException(); - } - channel.write(buf); + public void write(int oneByte) throws IOException { + checkBlocking(); + out.write(oneByte); } @Override - public void write(int oneByte) throws IOException { + public void write(byte[] buffer) throws IOException { + checkBlocking(); + out.write(buffer); + } + + @Override + public void close() throws IOException { + super.close(); + // channel.close() recognizes the socket is closed and avoids recursion. There is no + // channel.onClose() because the "closed" field is private. + channel.close(); + } + + private void checkBlocking() { if (!channel.isBlocking()) { throw new IllegalBlockingModeException(); } - ByteBuffer buffer = ByteBuffer.allocate(1); - buffer.put(0, (byte) (oneByte & 0xFF)); - channel.write(buffer); } } /* - * This input stream delegates all operations to the associated channel. * Throws an IllegalBlockingModeException if the channel is in non-blocking * mode when performing read operations. */ - private static class SocketChannelInputStream extends InputStream { + private static class BlockingCheckInputStream extends FilterInputStream { private final SocketChannel channel; - public SocketChannelInputStream(SocketChannel channel) { + public BlockingCheckInputStream(InputStream in, SocketChannel channel) { + super(in); this.channel = channel; } - /* - * Closes this stream and channel. - */ @Override - public void close() throws IOException { - channel.close(); + public int read() throws IOException { + checkBlocking(); + return in.read(); } @Override - public int read() throws IOException { - if (!channel.isBlocking()) { - throw new IllegalBlockingModeException(); - } - ByteBuffer buf = ByteBuffer.allocate(1); - int result = channel.read(buf); - return (result == -1) ? result : (buf.get(0) & 0xff); + public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { + checkBlocking(); + return in.read(buffer, byteOffset, byteCount); } @Override - public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { - Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount); + public int read(byte[] buffer) throws IOException { + checkBlocking(); + return in.read(buffer); + } + + @Override + public void close() throws IOException { + super.close(); + // channel.close() recognizes the socket is closed and avoids recursion. There is no + // channel.onClose() because the "closed" field is private. + channel.close(); + } + + private void checkBlocking() { if (!channel.isBlocking()) { throw new IllegalBlockingModeException(); } - ByteBuffer buf = ByteBuffer.wrap(buffer, byteOffset, byteCount); - return channel.read(buf); } } } diff --git a/luni/src/main/java/java/nio/channels/DatagramChannel.java b/luni/src/main/java/java/nio/channels/DatagramChannel.java index 486b168..2cff7f0 100644 --- a/luni/src/main/java/java/nio/channels/DatagramChannel.java +++ b/luni/src/main/java/java/nio/channels/DatagramChannel.java @@ -19,10 +19,13 @@ package java.nio.channels; import java.io.IOException; import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.NetworkInterface; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.SelectorProvider; +import java.util.Set; /** * A {@code DatagramChannel} is a selectable channel that represents a partial diff --git a/luni/src/main/java/java/nio/channels/FileChannel.java b/luni/src/main/java/java/nio/channels/FileChannel.java index 298ea8b..d6c140b 100644 --- a/luni/src/main/java/java/nio/channels/FileChannel.java +++ b/luni/src/main/java/java/nio/channels/FileChannel.java @@ -77,7 +77,7 @@ import java.nio.channels.spi.AbstractInterruptibleChannel; * content, size, etc. */ public abstract class FileChannel extends AbstractInterruptibleChannel - implements GatheringByteChannel, ScatteringByteChannel, ByteChannel { + implements ByteChannel, GatheringByteChannel, ScatteringByteChannel { /** * {@code MapMode} defines file mapping mode constants. @@ -281,10 +281,9 @@ public abstract class FileChannel extends AbstractInterruptibleChannel long position, long size) throws IOException; /** - * Returns the current value of the file position pointer. + * Returns the current position as a positive integer number of bytes from + * the start of the file. * - * @return the current position as a positive integer number of bytes from - * the start of the file. * @throws ClosedChannelException * if this channel is closed. * @throws IOException @@ -302,9 +301,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel * succeed but they will fill the bytes between the current end of file and * the new position with the required number of (unspecified) byte values. * - * @param offset - * the new file position, in bytes. - * @return the receiver. + * @return this. * @throws IllegalArgumentException * if the new position is negative. * @throws ClosedChannelException @@ -312,7 +309,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel * @throws IOException * if another I/O error occurs. */ - public abstract FileChannel position(long offset) throws IOException; + public abstract FileChannel position(long newPosition) throws IOException; /** * Reads bytes from this file channel into the given buffer. @@ -362,7 +359,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel * the buffer to receive the bytes. * @param position * the (non-negative) position at which to read the bytes. - * @return the number of bytes actually read. + * @return the number of bytes actually read, or -1 if the end of the file has been reached. * @throws AsynchronousCloseException * if this channel is closed by another thread while this method * is executing. @@ -398,7 +395,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel * * @param buffers * the array of byte buffers into which the bytes will be copied. - * @return the number of bytes actually read. + * @return the number of bytes actually read, or -1 if the end of the file has been reached. * @throws AsynchronousCloseException * if this channel is closed by another thread during this read * operation. @@ -413,6 +410,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel * if the channel has not been opened in a mode that permits * reading. */ + @Override public final long read(ByteBuffer[] buffers) throws IOException { return read(buffers, 0, buffers.length); } @@ -433,7 +431,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel * the index of the first buffer to store bytes in. * @param number * the maximum number of buffers to store bytes in. - * @return the number of bytes actually read. + * @return the number of bytes actually read, or -1 if the end of the file has been reached. * @throws AsynchronousCloseException * if this channel is closed by another thread during this read * operation. @@ -458,7 +456,6 @@ public abstract class FileChannel extends AbstractInterruptibleChannel /** * Returns the size of the file underlying this channel in bytes. * - * @return the size of the file in bytes. * @throws ClosedChannelException * if this channel is closed. * @throws IOException @@ -712,6 +709,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel * @throws NonWritableChannelException * if this channel was not opened for writing. */ + @Override public final long write(ByteBuffer[] buffers) throws IOException { return write(buffers, 0, buffers.length); } @@ -752,6 +750,7 @@ public abstract class FileChannel extends AbstractInterruptibleChannel * @throws NonWritableChannelException * if this channel was not opened for writing. */ + @Override public abstract long write(ByteBuffer[] buffers, int offset, int length) throws IOException; } diff --git a/luni/src/main/java/java/nio/channels/FileLock.java b/luni/src/main/java/java/nio/channels/FileLock.java index 360f826..5b26475 100644 --- a/luni/src/main/java/java/nio/channels/FileLock.java +++ b/luni/src/main/java/java/nio/channels/FileLock.java @@ -108,8 +108,6 @@ public abstract class FileLock implements AutoCloseable { /** * Returns the lock's {@link FileChannel}. - * - * @return the channel. */ public final FileChannel channel() { return channel; diff --git a/luni/src/main/java/java/nio/channels/ServerSocketChannel.java b/luni/src/main/java/java/nio/channels/ServerSocketChannel.java index f3c3390..ef50155 100644 --- a/luni/src/main/java/java/nio/channels/ServerSocketChannel.java +++ b/luni/src/main/java/java/nio/channels/ServerSocketChannel.java @@ -19,8 +19,10 @@ package java.nio.channels; import java.io.IOException; import java.net.ServerSocket; +import java.net.SocketAddress; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.SelectorProvider; +import java.util.Set; /** * A {@code ServerSocketChannel} is a partial abstraction of a selectable, @@ -35,7 +37,6 @@ import java.nio.channels.spi.SelectorProvider; * related {@code ServerSocket} instance. */ public abstract class ServerSocketChannel extends AbstractSelectableChannel { - /** * Constructs a new {@link ServerSocketChannel}. * diff --git a/luni/src/main/java/java/nio/channels/SocketChannel.java b/luni/src/main/java/java/nio/channels/SocketChannel.java index 753c88f..a91fccd 100644 --- a/luni/src/main/java/java/nio/channels/SocketChannel.java +++ b/luni/src/main/java/java/nio/channels/SocketChannel.java @@ -23,36 +23,44 @@ import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.SelectorProvider; +import java.util.Set; /** * A {@code SocketChannel} is a selectable channel that provides a partial - * abstraction of stream connecting socket. {@code socket()} returns the related - * {@link Socket} instance which can handle the socket. - * <p> - * A socket channel is open but not connected when created by {@code open()}. - * After connecting it by calling {@code connect(SocketAddress)}, it will remain - * connected until it gets closed. If the connection is non-blocking then - * {@code connect(SocketAddress)} is used to initiate the connection, followed - * by a call of {@code finishConnect()} to perform the final steps of - * connecting. {@code isConnectionPending()} indicates if the connection is - * blocked or not; {@code isConnected()} indicates if the socket is finally - * connected or not. - * <p> - * The input and output sides of a channel can be shut down independently and - * asynchronously without closing the channel. The {@code shutdownInput} method + * abstraction of stream connecting socket. + * + * The {@link #socket()} method returns a {@link Socket} instance which + * allows a wider range of socket operations than {@code SocketChannel} itself. + * + * <p>A socket channel is open but not connected when created by {@link #open}. + * After connecting it by calling {@link #connect(SocketAddress)}, it will remain + * connected until closed. + * + * <p>If the connection is non-blocking then + * {@link #connect(SocketAddress)} is used to initiate the connection, followed + * by a call of {@link #finishConnect} to perform the final steps of + * connecting. {@link #isConnectionPending} to tests whether we're still + * trying to connect; {@link #isConnected} tests whether the socket connect + * completed successfully. Note that realistic code should use a {@link Selector} + * instead of polling. Note also that {@link java.net.Socket} can connect with a + * timeout, which is the most common use for a non-blocking connect. + * + * <p>The input and output sides of a channel can be shut down independently and + * asynchronously without closing the channel. The {@link Socket#shutdownInput} method + * on the socket returned by {@link #socket} * is used for the input side of a channel and subsequent read operations return * -1, which means end of stream. If another thread is blocked in a read * operation when the shutdown occurs, the read will end without effect and - * return end of stream. The {@code shutdownOutput} method is used for the + * return end of stream. Likewise the {@link Socket#shutdownOutput} method is used for the * output side of the channel; subsequent write operations throw a * {@link ClosedChannelException}. If the output is shut down and another thread * is blocked in a write operation, an {@link AsynchronousCloseException} will * be thrown to the pending thread. - * <p> - * Socket channels are thread-safe, no more than one thread can read or write at - * any given time. The {@code connect(SocketAddress)} and {@code - * finishConnect()} methods are synchronized against each other; when they are - * processing, calls to {@code read} and {@code write} will block. + * + * <p>Socket channels are thread-safe, no more than one thread can read or write at + * any given time. The {@link #connect(SocketAddress)} and {@link + * #finishConnect()} methods are synchronized against each other; when they are + * processing, calls to {@link #read} and {@link #write} will block. */ public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel { diff --git a/luni/src/main/java/java/nio/charset/CharsetDecoderICU.java b/luni/src/main/java/java/nio/charset/CharsetDecoderICU.java index 8b32efa..5dfdd9f 100644 --- a/luni/src/main/java/java/nio/charset/CharsetDecoderICU.java +++ b/luni/src/main/java/java/nio/charset/CharsetDecoderICU.java @@ -25,13 +25,13 @@ final class CharsetDecoderICU extends CharsetDecoder { private static final int INPUT_OFFSET = 0; private static final int OUTPUT_OFFSET = 1; - private static final int INVALID_BYTES = 2; + private static final int INVALID_BYTE_COUNT = 2; /* * data[INPUT_OFFSET] = on input contains the start of input and on output the number of input bytes consumed * data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output chars written - * data[INVALID_BYTES] = number of invalid bytes + * data[INVALID_BYTE_COUNT] = number of invalid bytes */ - private int[] data = new int[3]; + private final int[] data = new int[3]; /* handle to the ICU converter that is opened */ private long converterHandle = 0; @@ -90,7 +90,7 @@ final class CharsetDecoderICU extends CharsetDecoder { NativeConverter.resetByteToChar(converterHandle); data[INPUT_OFFSET] = 0; data[OUTPUT_OFFSET] = 0; - data[INVALID_BYTES] = 0; + data[INVALID_BYTE_COUNT] = 0; output = null; input = null; allocatedInput = null; @@ -107,15 +107,15 @@ final class CharsetDecoderICU extends CharsetDecoder { data[INPUT_OFFSET] = 0; data[OUTPUT_OFFSET] = getArray(out); - data[INVALID_BYTES] = 0; // Make sure we don't see earlier errors. + data[INVALID_BYTE_COUNT] = 0; // Make sure we don't see earlier errors. int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true); if (ICU.U_FAILURE(error)) { if (error == ICU.U_BUFFER_OVERFLOW_ERROR) { return CoderResult.OVERFLOW; } else if (error == ICU.U_TRUNCATED_CHAR_FOUND) { - if (data[INPUT_OFFSET] > 0) { - return CoderResult.malformedForLength(data[INPUT_OFFSET]); + if (data[INVALID_BYTE_COUNT] > 0) { + return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]); } } } @@ -140,9 +140,9 @@ final class CharsetDecoderICU extends CharsetDecoder { if (error == ICU.U_BUFFER_OVERFLOW_ERROR) { return CoderResult.OVERFLOW; } else if (error == ICU.U_INVALID_CHAR_FOUND) { - return CoderResult.unmappableForLength(data[INVALID_BYTES]); + return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]); } else if (error == ICU.U_ILLEGAL_CHAR_FOUND) { - return CoderResult.malformedForLength(data[INVALID_BYTES]); + return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]); } else { throw new AssertionError(error); } diff --git a/luni/src/main/java/java/nio/charset/CharsetEncoderICU.java b/luni/src/main/java/java/nio/charset/CharsetEncoderICU.java index 4bc4354..3583e19 100644 --- a/luni/src/main/java/java/nio/charset/CharsetEncoderICU.java +++ b/luni/src/main/java/java/nio/charset/CharsetEncoderICU.java @@ -40,7 +40,7 @@ final class CharsetEncoderICU extends CharsetEncoder { private static final int INPUT_OFFSET = 0; private static final int OUTPUT_OFFSET = 1; - private static final int INVALID_CHARS = 2; + private static final int INVALID_CHAR_COUNT = 2; /* * data[INPUT_OFFSET] = on input contains the start of input and on output the number of input chars consumed * data[OUTPUT_OFFSET] = on input contains the start of output and on output the number of output bytes written @@ -118,7 +118,7 @@ final class CharsetEncoderICU extends CharsetEncoder { NativeConverter.resetCharToByte(converterHandle); data[INPUT_OFFSET] = 0; data[OUTPUT_OFFSET] = 0; - data[INVALID_CHARS] = 0; + data[INVALID_CHAR_COUNT] = 0; output = null; input = null; allocatedInput = null; @@ -135,15 +135,15 @@ final class CharsetEncoderICU extends CharsetEncoder { data[INPUT_OFFSET] = 0; data[OUTPUT_OFFSET] = getArray(out); - data[INVALID_CHARS] = 0; // Make sure we don't see earlier errors. + data[INVALID_CHAR_COUNT] = 0; // Make sure we don't see earlier errors. int error = NativeConverter.encode(converterHandle, input, inEnd, output, outEnd, data, true); if (ICU.U_FAILURE(error)) { if (error == ICU.U_BUFFER_OVERFLOW_ERROR) { return CoderResult.OVERFLOW; } else if (error == ICU.U_TRUNCATED_CHAR_FOUND) { - if (data[INPUT_OFFSET] > 0) { - return CoderResult.malformedForLength(data[INPUT_OFFSET]); + if (data[INVALID_CHAR_COUNT] > 0) { + return CoderResult.malformedForLength(data[INVALID_CHAR_COUNT]); } } } @@ -161,7 +161,7 @@ final class CharsetEncoderICU extends CharsetEncoder { data[INPUT_OFFSET] = getArray(in); data[OUTPUT_OFFSET]= getArray(out); - data[INVALID_CHARS] = 0; // Make sure we don't see earlier errors. + data[INVALID_CHAR_COUNT] = 0; // Make sure we don't see earlier errors. try { int error = NativeConverter.encode(converterHandle, input, inEnd, output, outEnd, data, false); @@ -169,9 +169,9 @@ final class CharsetEncoderICU extends CharsetEncoder { if (error == ICU.U_BUFFER_OVERFLOW_ERROR) { return CoderResult.OVERFLOW; } else if (error == ICU.U_INVALID_CHAR_FOUND) { - return CoderResult.unmappableForLength(data[INVALID_CHARS]); + return CoderResult.unmappableForLength(data[INVALID_CHAR_COUNT]); } else if (error == ICU.U_ILLEGAL_CHAR_FOUND) { - return CoderResult.malformedForLength(data[INVALID_CHARS]); + return CoderResult.malformedForLength(data[INVALID_CHAR_COUNT]); } else { throw new AssertionError(error); } @@ -231,7 +231,7 @@ final class CharsetEncoderICU extends CharsetEncoder { private void setPosition(ByteBuffer out) { if (out.hasArray()) { - out.position(out.position() + data[OUTPUT_OFFSET] - out.arrayOffset()); + out.position(data[OUTPUT_OFFSET] - out.arrayOffset()); } else { out.put(output, 0, data[OUTPUT_OFFSET]); } @@ -240,7 +240,17 @@ final class CharsetEncoderICU extends CharsetEncoder { } private void setPosition(CharBuffer in) { - in.position(in.position() + data[INPUT_OFFSET] - data[INVALID_CHARS]); + int position = in.position() + data[INPUT_OFFSET] - data[INVALID_CHAR_COUNT]; + if (position < 0) { + // The calculated position might be negative if we encountered an + // invalid char that spanned input buffers. We adjust it to 0 in this case. + // + // NOTE: The API doesn't allow us to adjust the position of the previous + // input buffer. (Doing that wouldn't serve any useful purpose anyway.) + position = 0; + } + + in.position(position); // release reference to input array, which may not be ours input = null; } diff --git a/luni/src/main/java/java/security/AlgorithmParameterGenerator.java b/luni/src/main/java/java/security/AlgorithmParameterGenerator.java index 61548d7..167204c 100644 --- a/luni/src/main/java/java/security/AlgorithmParameterGenerator.java +++ b/luni/src/main/java/java/security/AlgorithmParameterGenerator.java @@ -129,7 +129,8 @@ public class AlgorithmParameterGenerator { /** * Returns a new instance of {@code AlgorithmParameterGenerator} from the - * specified provider for the specified algorithm. + * specified provider for the specified algorithm. The {@code provider} + * supplied does not have to be registered. * * @param algorithm * the name of the algorithm to use. diff --git a/luni/src/main/java/java/security/AlgorithmParameters.java b/luni/src/main/java/java/security/AlgorithmParameters.java index 073460e..27af018 100644 --- a/luni/src/main/java/java/security/AlgorithmParameters.java +++ b/luni/src/main/java/java/security/AlgorithmParameters.java @@ -131,7 +131,8 @@ public class AlgorithmParameters { /** * Returns a new instance of {@code AlgorithmParameters} from the specified - * provider for the specified algorithm. + * provider for the specified algorithm. The {@code provider} supplied does + * not have to be registered. * * @param algorithm * the name of the algorithm to use. diff --git a/luni/src/main/java/java/security/GuardedObject.java b/luni/src/main/java/java/security/GuardedObject.java index 34a5113..dd2e98f 100644 --- a/luni/src/main/java/java/security/GuardedObject.java +++ b/luni/src/main/java/java/security/GuardedObject.java @@ -53,7 +53,7 @@ public class GuardedObject implements Serializable { * SecurityException} is thrown. * * @return the guarded object. - * @exception SecurityException + * @throws SecurityException * if access is not granted to the guarded object. */ public Object getObject() throws SecurityException { diff --git a/luni/src/main/java/java/security/KeyFactory.java b/luni/src/main/java/java/security/KeyFactory.java index 3bd05b9..c22212f 100644 --- a/luni/src/main/java/java/security/KeyFactory.java +++ b/luni/src/main/java/java/security/KeyFactory.java @@ -112,7 +112,8 @@ public class KeyFactory { /** * Returns a new instance of {@code KeyFactory} that utilizes the specified - * algorithm from the specified provider. + * algorithm from the specified provider. The {@code provider} supplied + * does not have to be registered. * * @param algorithm * the name of the algorithm. diff --git a/luni/src/main/java/java/security/KeyPairGenerator.java b/luni/src/main/java/java/security/KeyPairGenerator.java index 8a713d0..d05e902 100644 --- a/luni/src/main/java/java/security/KeyPairGenerator.java +++ b/luni/src/main/java/java/security/KeyPairGenerator.java @@ -124,7 +124,8 @@ public abstract class KeyPairGenerator extends KeyPairGeneratorSpi { /** * Returns a new instance of {@code KeyPairGenerator} that utilizes the - * specified algorithm from the specified provider. + * specified algorithm from the specified provider. The {@code provider} + * supplied does not have to be registered. * * @param algorithm * the name of the algorithm to use diff --git a/luni/src/main/java/java/security/KeyStore.java b/luni/src/main/java/java/security/KeyStore.java index b0e4945..0611e59 100644 --- a/luni/src/main/java/java/security/KeyStore.java +++ b/luni/src/main/java/java/security/KeyStore.java @@ -159,7 +159,8 @@ public class KeyStore { /** * Returns a new instance of {@code KeyStore} from the specified provider - * with the given type. + * with the given type. The {@code provider} supplied does not have to be + * registered. * * @param type * the type of the returned {@code KeyStore}. diff --git a/luni/src/main/java/java/security/MessageDigest.java b/luni/src/main/java/java/security/MessageDigest.java index 658b41f..1d37a90 100644 --- a/luni/src/main/java/java/security/MessageDigest.java +++ b/luni/src/main/java/java/security/MessageDigest.java @@ -132,7 +132,8 @@ public abstract class MessageDigest extends MessageDigestSpi { /** * Returns a new instance of {@code MessageDigest} that utilizes the - * specified algorithm from the specified provider. + * specified algorithm from the specified provider. The + * {@code provider} supplied does not have to be registered. * * @param algorithm * the name of the algorithm to use @@ -302,12 +303,12 @@ public abstract class MessageDigest extends MessageDigestSpi { if (digesta.length != digestb.length) { return false; } + // Perform a constant time comparison to avoid timing attacks. + int v = 0; for (int i = 0; i < digesta.length; i++) { - if (digesta[i] != digestb[i]) { - return false; - } + v |= (digesta[i] ^ digestb[i]); } - return true; + return v == 0; } /** @@ -351,14 +352,6 @@ public abstract class MessageDigest extends MessageDigestSpi { } } - @Override - public Object clone() throws CloneNotSupportedException { - if (this instanceof Cloneable) { - return super.clone(); - } - throw new CloneNotSupportedException(); - } - /** * Updates this {@code MessageDigest} using the given {@code input}. * @@ -420,12 +413,8 @@ public abstract class MessageDigest extends MessageDigestSpi { // Returns a clone if the spiImpl is cloneable @Override public Object clone() throws CloneNotSupportedException { - if (spiImpl instanceof Cloneable) { - MessageDigestSpi spi = (MessageDigestSpi) spiImpl.clone(); - return new MessageDigestImpl(spi, getProvider(), getAlgorithm()); - } - - throw new CloneNotSupportedException(); + MessageDigestSpi spi = (MessageDigestSpi) spiImpl.clone(); + return new MessageDigestImpl(spi, getProvider(), getAlgorithm()); } } } diff --git a/luni/src/main/java/java/security/Provider.java b/luni/src/main/java/java/security/Provider.java index a6de19c..1704b58 100644 --- a/luni/src/main/java/java/security/Provider.java +++ b/luni/src/main/java/java/security/Provider.java @@ -804,6 +804,79 @@ public abstract class Provider extends Properties { * provider it belongs and other properties. */ public static class Service { + /** Attribute name of supported key classes. */ + private static final String ATTR_SUPPORTED_KEY_CLASSES = "SupportedKeyClasses"; + + /** Attribute name of supported key formats. */ + private static final String ATTR_SUPPORTED_KEY_FORMATS = "SupportedKeyFormats"; + + /** Whether this type supports calls to {@link #supportsParameter(Object)}. */ + private static final HashMap<String, Boolean> supportsParameterTypes + = new HashMap<String, Boolean>(); + static { + // Does not support parameter + supportsParameterTypes.put("AlgorithmParameterGenerator", false); + supportsParameterTypes.put("AlgorithmParameters", false); + supportsParameterTypes.put("CertificateFactory", false); + supportsParameterTypes.put("CertPathBuilder", false); + supportsParameterTypes.put("CertPathValidator", false); + supportsParameterTypes.put("CertStore", false); + supportsParameterTypes.put("KeyFactory", false); + supportsParameterTypes.put("KeyGenerator", false); + supportsParameterTypes.put("KeyManagerFactory", false); + supportsParameterTypes.put("KeyPairGenerator", false); + supportsParameterTypes.put("KeyStore", false); + supportsParameterTypes.put("MessageDigest", false); + supportsParameterTypes.put("SecretKeyFactory", false); + supportsParameterTypes.put("SecureRandom", false); + supportsParameterTypes.put("SSLContext", false); + supportsParameterTypes.put("TrustManagerFactory", false); + + // Supports parameter + supportsParameterTypes.put("Cipher", true); + supportsParameterTypes.put("KeyAgreement", true); + supportsParameterTypes.put("Mac", true); + supportsParameterTypes.put("Signature", true); + } + + /** Constructor argument classes for calls to {@link #newInstance(Object)}. */ + private static final HashMap<String, Class<?>> constructorParameterClasses = new HashMap<String, Class<?>>(); + static { + // Types that take a parameter to newInstance + constructorParameterClasses.put("CertStore", + loadClassOrThrow("java.security.cert.CertStoreParameters")); + + // Types that do not take any kind of parameter + constructorParameterClasses.put("AlgorithmParameterGenerator", null); + constructorParameterClasses.put("AlgorithmParameters", null); + constructorParameterClasses.put("CertificateFactory", null); + constructorParameterClasses.put("CertPathBuilder", null); + constructorParameterClasses.put("CertPathValidator", null); + constructorParameterClasses.put("KeyFactory", null); + constructorParameterClasses.put("KeyGenerator", null); + constructorParameterClasses.put("KeyManagerFactory", null); + constructorParameterClasses.put("KeyPairGenerator", null); + constructorParameterClasses.put("KeyStore", null); + constructorParameterClasses.put("MessageDigest", null); + constructorParameterClasses.put("SecretKeyFactory", null); + constructorParameterClasses.put("SecureRandom", null); + constructorParameterClasses.put("SSLContext", null); + constructorParameterClasses.put("TrustManagerFactory", null); + constructorParameterClasses.put("Cipher", null); + constructorParameterClasses.put("KeyAgreement", null); + constructorParameterClasses.put("Mac", null); + constructorParameterClasses.put("Signature", null); + } + + /** Called to load a class if it's critical that the class exists. */ + private static Class<?> loadClassOrThrow(String className) { + try { + return Provider.class.getClassLoader().loadClass(className); + } catch (Exception e) { + throw new AssertionError(e); + } + } + // The provider private Provider provider; @@ -828,6 +901,15 @@ public abstract class Provider extends Properties { // For newInstance() optimization private String lastClassName; + /** Indicates whether supportedKeyClasses and supportedKeyFormats. */ + private volatile boolean supportedKeysInitialized; + + /** List of classes that this service supports. */ + private Class<?>[] keyClasses; + + /** List of key formats this service supports. */ + private String[] keyFormats; + /** * Constructs a new instance of {@code Service} with the given * attributes. @@ -993,28 +1075,52 @@ public abstract class Provider extends Properties { throw new NoSuchAlgorithmException(type + " " + algorithm + " implementation not found: " + e); } } - if (constructorParameter == null) { - try { - return implementation.newInstance(); - } catch (Exception e) { - throw new NoSuchAlgorithmException( - type + " " + algorithm + " implementation not found", e); + + // We don't know whether this takes a parameter or not. + if (!constructorParameterClasses.containsKey(type)) { + if (constructorParameter == null) { + return newInstanceNoParameter(); + } else { + return newInstanceWithParameter(constructorParameter, + constructorParameter.getClass()); } } - if (!supportsParameter(constructorParameter)) { - throw new InvalidParameterException(type + ": service cannot use the parameter"); + + // A known type, but it's not required to have a parameter even if a + // class is specified. + if (constructorParameter == null) { + return newInstanceNoParameter(); } - Class[] parameterTypes = new Class[1]; - Object[] initargs = { constructorParameter }; + // Make sure the provided constructor class is valid. + final Class<?> expectedClass = constructorParameterClasses.get(type); + if (expectedClass == null) { + throw new IllegalArgumentException("Constructor parameter not supported for " + + type); + } + if (!expectedClass.isAssignableFrom(constructorParameter.getClass())) { + throw new IllegalArgumentException("Expecting constructor parameter of type " + + expectedClass.getName() + " but was " + + constructorParameter.getClass().getName()); + } + return newInstanceWithParameter(constructorParameter, expectedClass); + } + + private Object newInstanceWithParameter(Object constructorParameter, + Class<?> parameterClass) throws NoSuchAlgorithmException { try { - if (type.equalsIgnoreCase("CertStore")) { - parameterTypes[0] = Class.forName("java.security.cert.CertStoreParameters"); - } else { - parameterTypes[0] = constructorParameter.getClass(); - } - return implementation.getConstructor(parameterTypes) - .newInstance(initargs); + Class<?>[] parameterTypes = { parameterClass }; + Object[] initargs = { constructorParameter }; + return implementation.getConstructor(parameterTypes).newInstance(initargs); + } catch (Exception e) { + throw new NoSuchAlgorithmException(type + " " + algorithm + + " implementation not found", e); + } + } + + private Object newInstanceNoParameter() throws NoSuchAlgorithmException { + try { + return implementation.newInstance(); } catch (Exception e) { throw new NoSuchAlgorithmException(type + " " + algorithm + " implementation not found", e); @@ -1031,7 +1137,111 @@ public abstract class Provider extends Properties { * constructor parameter, {@code false} otherwise. */ public boolean supportsParameter(Object parameter) { - return true; + Boolean supportsParameter = supportsParameterTypes.get(type); + if (supportsParameter == null) { + return true; + } + if (!supportsParameter) { + throw new InvalidParameterException("Cannot use a parameter with " + type); + } + + /* + * Only Key parameters are allowed, but allow null since there might + * not be any listed classes or formats for this instance. + */ + if (parameter != null && !(parameter instanceof Key)) { + throw new InvalidParameterException("Parameter should be of type Key"); + } + + ensureSupportedKeysInitialized(); + + // No restriction specified by Provider registration. + if (keyClasses == null && keyFormats == null) { + return true; + } + + // Restriction specified by registration, so null is not acceptable. + if (parameter == null) { + return false; + } + + Key keyParam = (Key) parameter; + if (keyClasses != null && isInArray(keyClasses, keyParam.getClass())) { + return true; + } + if (keyFormats != null && isInArray(keyFormats, keyParam.getFormat())) { + return true; + } + + return false; + } + + /** + * Initialize the list of supported key classes and formats. + */ + private void ensureSupportedKeysInitialized() { + if (supportedKeysInitialized) { + return; + } + + final String supportedClassesString = getAttribute(ATTR_SUPPORTED_KEY_CLASSES); + if (supportedClassesString != null) { + String[] keyClassNames = supportedClassesString.split("\\|"); + ArrayList<Class<?>> supportedClassList = new ArrayList<Class<?>>( + keyClassNames.length); + final ClassLoader classLoader = getProvider().getClass().getClassLoader(); + for (String keyClassName : keyClassNames) { + try { + Class<?> keyClass = classLoader.loadClass(keyClassName); + if (Key.class.isAssignableFrom(keyClass)) { + supportedClassList.add(keyClass); + } + } catch (ClassNotFoundException ignored) { + } + } + keyClasses = supportedClassList.toArray(new Class<?>[supportedClassList.size()]); + } + + final String supportedFormatString = getAttribute(ATTR_SUPPORTED_KEY_FORMATS); + if (supportedFormatString != null) { + keyFormats = supportedFormatString.split("\\|"); + } + + supportedKeysInitialized = true; + } + + /** + * Check if an item is in the array. The array of supported key classes + * and formats is usually just a length of 1, so a simple array is + * faster than a Set. + */ + private static <T> boolean isInArray(T[] itemList, T target) { + if (target == null) { + return false; + } + for (T item : itemList) { + if (target.equals(item)) { + return true; + } + } + return false; + } + + /** + * Check if an item is in the array. The array of supported key classes + * and formats is usually just a length of 1, so a simple array is + * faster than a Set. + */ + private static boolean isInArray(Class<?>[] itemList, Class<?> target) { + if (target == null) { + return false; + } + for (Class<?> item : itemList) { + if (item.isAssignableFrom(target)) { + return true; + } + } + return false; } /** diff --git a/luni/src/main/java/java/security/SecureRandom.java b/luni/src/main/java/java/security/SecureRandom.java index 281885b..7a03801 100644 --- a/luni/src/main/java/java/security/SecureRandom.java +++ b/luni/src/main/java/java/security/SecureRandom.java @@ -46,8 +46,8 @@ import org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl; * For deriving keys from passwords, see * {@link javax.crypto.SecretKeyFactory}. * - * <h3><a name="insecure_seed">Seeding {@code SecureRandom} may be - * insecure</a></h3> + * <h3><a name="insecure_seed"></a>Seeding {@code SecureRandom} may be + * insecure</h3> * A seed is an array of bytes used to bootstrap random number generation. * To produce cryptographically secure random numbers, both the seed and the * algorithm must be secure. @@ -190,7 +190,8 @@ public class SecureRandom extends Random { /** * Returns a new instance of {@code SecureRandom} that utilizes the - * specified algorithm from the specified provider. + * specified algorithm from the specified provider. The + * {@code provider} supplied does not have to be registered. * * @param algorithm * the name of the algorithm to use. diff --git a/luni/src/main/java/java/security/Security.java b/luni/src/main/java/java/security/Security.java index 0b6961b..b859f9a 100644 --- a/luni/src/main/java/java/security/Security.java +++ b/luni/src/main/java/java/security/Security.java @@ -19,6 +19,7 @@ package java.security; import java.io.BufferedInputStream; import java.io.InputStream; +import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; @@ -72,10 +73,9 @@ public final class Security { // Register default providers private static void registerDefaultProviders() { secprops.put("security.provider.1", "com.android.org.conscrypt.OpenSSLProvider"); - secprops.put("security.provider.2", "org.apache.harmony.security.provider.cert.DRLCertFactory"); - secprops.put("security.provider.3", "com.android.org.bouncycastle.jce.provider.BouncyCastleProvider"); - secprops.put("security.provider.4", "org.apache.harmony.security.provider.crypto.CryptoProvider"); - secprops.put("security.provider.5", "com.android.org.conscrypt.JSSEProvider"); + secprops.put("security.provider.2", "com.android.org.bouncycastle.jce.provider.BouncyCastleProvider"); + secprops.put("security.provider.3", "org.apache.harmony.security.provider.crypto.CryptoProvider"); + secprops.put("security.provider.4", "com.android.org.conscrypt.JSSEProvider"); } /** @@ -96,7 +96,7 @@ public final class Security { String prop = "Alg." + propName + "." + algName; Provider[] providers = getProviders(); for (Provider provider : providers) { - for (Enumeration e = provider.propertyNames(); e.hasMoreElements(); ) { + for (Enumeration<?> e = provider.propertyNames(); e.hasMoreElements();) { String propertyName = (String) e.nextElement(); if (propertyName.equalsIgnoreCase(prop)) { return provider.getProperty(propertyName); @@ -184,7 +184,8 @@ public final class Security { * @return an array containing all installed providers. */ public static synchronized Provider[] getProviders() { - return Services.getProviders(); + ArrayList<Provider> providers = Services.getProviders(); + return providers.toArray(new Provider[providers.size()]); } /** @@ -274,7 +275,7 @@ public final class Security { if (filter.isEmpty()) { return null; } - java.util.List<Provider> result = Services.getProvidersList(); + ArrayList<Provider> result = new ArrayList<Provider>(Services.getProviders()); Set<Entry<String, String>> keys = filter.entrySet(); Map.Entry<String, String> entry; for (Iterator<Entry<String, String>> it = keys.iterator(); it.hasNext();) { @@ -306,18 +307,7 @@ public final class Security { if (serv.length() == 0 || alg.length() == 0) { throw new InvalidParameterException(); } - Provider p; - for (int k = 0; k < result.size(); k++) { - try { - p = result.get(k); - } catch (IndexOutOfBoundsException e) { - break; - } - if (!p.implementsAlg(serv, alg, attribute, val)) { - result.remove(p); - k--; - } - } + filterProviders(result, serv, alg, attribute, val); } if (result.size() > 0) { return result.toArray(new Provider[result.size()]); @@ -325,6 +315,17 @@ public final class Security { return null; } + private static void filterProviders(ArrayList<Provider> providers, String service, + String algorithm, String attribute, String attrValue) { + Iterator<Provider> it = providers.iterator(); + while (it.hasNext()) { + Provider p = it.next(); + if (!p.implementsAlg(service, algorithm, attribute, attrValue)) { + it.remove(); + } + } + } + /** * Returns the value of the security property named by the argument. * @@ -347,6 +348,7 @@ public final class Security { * Sets the value of the specified security property. */ public static void setProperty(String key, String value) { + Services.setNeedRefresh(); secprops.put(key, value); } @@ -384,9 +386,9 @@ public final class Security { * */ private static void renumProviders() { - Provider[] p = Services.getProviders(); - for (int i = 0; i < p.length; i++) { - p[i].setProviderNumber(i + 1); + ArrayList<Provider> providers = Services.getProviders(); + for (int i = 0; i < providers.size(); i++) { + providers.get(i).setProviderNumber(i + 1); } } diff --git a/luni/src/main/java/java/security/Signature.java b/luni/src/main/java/java/security/Signature.java index 0372c33..a39d59b 100644 --- a/luni/src/main/java/java/security/Signature.java +++ b/luni/src/main/java/java/security/Signature.java @@ -21,10 +21,11 @@ import java.nio.ByteBuffer; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; import java.util.Iterator; import java.util.Set; import org.apache.harmony.security.fortress.Engine; - +import org.apache.harmony.security.fortress.Engine.SpiAndProvider; /** * {@code Signature} is an engine class which is capable of creating and @@ -39,13 +40,13 @@ public abstract class Signature extends SignatureSpi { private static final String SERVICE = "Signature"; // Used to access common engine functionality - private static Engine ENGINE = new Engine(SERVICE); + private static final Engine ENGINE = new Engine(SERVICE); // The provider - private Provider provider; + Provider provider; // The algorithm. - private String algorithm; + final String algorithm; /** * Constant that indicates that this {@code Signature} instance has not yet @@ -101,16 +102,7 @@ public abstract class Signature extends SignatureSpi { if (algorithm == null) { throw new NullPointerException("algorithm == null"); } - Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null); - Object spi = sap.spi; - Provider provider = sap.provider; - if (spi instanceof Signature) { - Signature result = (Signature) spi; - result.algorithm = algorithm; - result.provider = provider; - return result; - } - return new SignatureImpl((SignatureSpi) spi, provider, algorithm); + return getSignature(algorithm, null); } /** @@ -143,12 +135,13 @@ public abstract class Signature extends SignatureSpi { if (p == null) { throw new NoSuchProviderException(provider); } - return getSignatureInstance(algorithm, p); + return getSignature(algorithm, p); } /** * Returns a new instance of {@code Signature} that utilizes the specified - * algorithm from the specified provider. + * algorithm from the specified provider. The {@code provider} supplied + * does not have to be registered. * * @param algorithm * the name of the algorithm to use. @@ -170,19 +163,68 @@ public abstract class Signature extends SignatureSpi { if (provider == null) { throw new IllegalArgumentException("provider == null"); } - return getSignatureInstance(algorithm, provider); + return getSignature(algorithm, provider); + } + + private static Signature getSignature(String algorithm, Provider provider) + throws NoSuchAlgorithmException { + if (algorithm == null || algorithm.isEmpty()) { + throw new NoSuchAlgorithmException("Unknown algorithm: " + algorithm); + } + + SpiAndProvider spiAndProvider = tryAlgorithm(null, provider, algorithm); + if (spiAndProvider == null) { + if (provider == null) { + throw new NoSuchAlgorithmException("No provider found for " + algorithm); + } else { + throw new NoSuchAlgorithmException("Provider " + provider.getName() + + " does not provide " + algorithm); + } + } + if (spiAndProvider.spi instanceof Signature) { + return (Signature) spiAndProvider.spi; + } + return new SignatureImpl(algorithm, provider); + } + + private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) { + if (provider != null) { + Provider.Service service = provider.getService(SERVICE, algorithm); + if (service == null) { + return null; + } + return tryAlgorithmWithProvider(key, service); + } + ArrayList<Provider.Service> services = ENGINE.getServices(algorithm); + if (services == null) { + return null; + } + for (Provider.Service service : services) { + Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service); + if (sap != null) { + return sap; + } + } + return null; } - private static Signature getSignatureInstance(String algorithm, - Provider provider) throws NoSuchAlgorithmException { - Object spi = ENGINE.getInstance(algorithm, provider, null); - if (spi instanceof Signature) { - Signature result = (Signature) spi; - result.algorithm = algorithm; - result.provider = provider; - return result; + private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) { + try { + if (key != null && !service.supportsParameter(key)) { + return null; + } + + Engine.SpiAndProvider sap = ENGINE.getInstance(service, null); + if (sap.spi == null || sap.provider == null) { + return null; + } + if (!(sap.spi instanceof SignatureSpi)) { + return null; + } + return sap; + } catch (NoSuchAlgorithmException ignored) { } - return new SignatureImpl((SignatureSpi) spi, provider, algorithm); + return null; } /** @@ -191,10 +233,18 @@ public abstract class Signature extends SignatureSpi { * @return the provider associated with this {@code Signature}. */ public final Provider getProvider() { + ensureProviderChosen(); return provider; } /** + * This makes sure the provider is chosen since Signature is abstract and + * getProvider is final but we need to support late binding. + */ + void ensureProviderChosen() { + } + + /** * Returns the name of the algorithm of this {@code Signature}. * * @return the name of the algorithm of this {@code Signature}. @@ -237,10 +287,10 @@ public abstract class Signature extends SignatureSpi { public final void initVerify(Certificate certificate) throws InvalidKeyException { if (certificate instanceof X509Certificate) { - Set ce = ((X509Certificate) certificate).getCriticalExtensionOIDs(); + Set<String> ce = ((X509Certificate) certificate).getCriticalExtensionOIDs(); boolean critical = false; if (ce != null && !ce.isEmpty()) { - for (Iterator i = ce.iterator(); i.hasNext();) { + for (Iterator<String> i = ce.iterator(); i.hasNext();) { if ("2.5.29.15".equals(i.next())) { //KeyUsage OID = 2.5.29.15 critical = true; @@ -574,92 +624,115 @@ public abstract class Signature extends SignatureSpi { return engineGetParameter(param); } - @Override - public Object clone() throws CloneNotSupportedException { - if (this instanceof Cloneable) { - return super.clone(); - } - throw new CloneNotSupportedException(); - } - /** - * * Internal Signature implementation - * */ private static class SignatureImpl extends Signature { + /** + * Lock held while the SPI is initializing. + */ + private final Object initLock = new Object(); + + // The provider specified when creating this instance. + private final Provider specifiedProvider; + private SignatureSpi spiImpl; - // Constructor - public SignatureImpl(SignatureSpi signatureSpi, Provider provider, - String algorithm) { + public SignatureImpl(String algorithm, Provider provider) { super(algorithm); - super.provider = provider; - spiImpl = signatureSpi; + this.specifiedProvider = provider; + } + + private SignatureImpl(String algorithm, Provider provider, SignatureSpi spi) { + this(algorithm, provider); + spiImpl = spi; + } + + @Override + void ensureProviderChosen() { + getSpi(null); } - // engineSign() implementation @Override protected byte[] engineSign() throws SignatureException { - return spiImpl.engineSign(); + return getSpi().engineSign(); } - // engineUpdate() implementation @Override protected void engineUpdate(byte arg0) throws SignatureException { - spiImpl.engineUpdate(arg0); + getSpi().engineUpdate(arg0); } - // engineVerify() implementation @Override protected boolean engineVerify(byte[] arg0) throws SignatureException { - return spiImpl.engineVerify(arg0); + return getSpi().engineVerify(arg0); } - // engineUpdate() implementation @Override - protected void engineUpdate(byte[] arg0, int arg1, int arg2) - throws SignatureException { - spiImpl.engineUpdate(arg0, arg1, arg2); + protected void engineUpdate(byte[] arg0, int arg1, int arg2) throws SignatureException { + getSpi().engineUpdate(arg0, arg1, arg2); } - // engineInitSign() implementation @Override - protected void engineInitSign(PrivateKey arg0) - throws InvalidKeyException { - spiImpl.engineInitSign(arg0); + protected void engineInitSign(PrivateKey arg0) throws InvalidKeyException { + getSpi(arg0).engineInitSign(arg0); } - // engineInitVerify() implementation @Override - protected void engineInitVerify(PublicKey arg0) - throws InvalidKeyException { - spiImpl.engineInitVerify(arg0); + protected void engineInitVerify(PublicKey arg0) throws InvalidKeyException { + getSpi(arg0).engineInitVerify(arg0); } - // engineGetParameter() implementation @Override - protected Object engineGetParameter(String arg0) - throws InvalidParameterException { - return spiImpl.engineGetParameter(arg0); + protected Object engineGetParameter(String arg0) throws InvalidParameterException { + return getSpi().engineGetParameter(arg0); } - // engineSetParameter() implementation @Override protected void engineSetParameter(String arg0, Object arg1) throws InvalidParameterException { - spiImpl.engineSetParameter(arg0, arg1); + getSpi().engineSetParameter(arg0, arg1); + } + + @Override + protected void engineSetParameter(AlgorithmParameterSpec arg0) + throws InvalidAlgorithmParameterException { + getSpi().engineSetParameter(arg0); } - // Returns a clone if the spiImpl is cloneable @Override public Object clone() throws CloneNotSupportedException { - if (spiImpl instanceof Cloneable) { - SignatureSpi spi = (SignatureSpi) spiImpl.clone(); - return new SignatureImpl(spi, getProvider(), getAlgorithm()); + SignatureSpi spi = spiImpl != null ? (SignatureSpi) spiImpl.clone() : null; + return new SignatureImpl(getAlgorithm(), getProvider(), spi); + } + + /** + * Makes sure a CipherSpi that matches this type is selected. + */ + private SignatureSpi getSpi(Key key) { + synchronized (initLock) { + if (spiImpl != null && key == null) { + return spiImpl; + } + + final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm); + if (sap == null) { + throw new ProviderException("No provider for " + getAlgorithm()); + } + + spiImpl = (SignatureSpi) sap.spi; + provider = sap.provider; + + return spiImpl; } - throw new CloneNotSupportedException(); + } + + /** + * Convenience call when the Key is not available. + */ + private SignatureSpi getSpi() { + return getSpi(null); } } } diff --git a/luni/src/main/java/java/security/SignatureSpi.java b/luni/src/main/java/java/security/SignatureSpi.java index 27be30c..66c43d7 100644 --- a/luni/src/main/java/java/security/SignatureSpi.java +++ b/luni/src/main/java/java/security/SignatureSpi.java @@ -307,9 +307,6 @@ public abstract class SignatureSpi { @Override public Object clone() throws CloneNotSupportedException { - if (this instanceof Cloneable) { - return super.clone(); - } - throw new CloneNotSupportedException(); + return super.clone(); } } diff --git a/luni/src/main/java/java/security/cert/CRLReason.java b/luni/src/main/java/java/security/cert/CRLReason.java new file mode 100644 index 0000000..6f663db --- /dev/null +++ b/luni/src/main/java/java/security/cert/CRLReason.java @@ -0,0 +1,40 @@ +/* + * Copyright 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 java.security.cert; + +import java.io.Serializable; + +/** + * The reason a CRL entry was revoked. See <a + * href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280</a> for more information. + * + * @since 1.7 + * @hide + */ +public enum CRLReason implements Comparable<CRLReason>, Serializable { + UNSPECIFIED, + KEY_COMPROMISE, + CA_COMPROMISE, + AFFILIATION_CHANGED, + SUPERSEDED, + CESSATION_OF_OPERATION, + CERTIFICATE_HOLD, + UNUSED, + REMOVE_FROM_CRL, + PRIVILEGE_WITHDRAWN, + AA_COMPROMISE; +} diff --git a/luni/src/main/java/java/security/cert/CertPathValidator.java b/luni/src/main/java/java/security/cert/CertPathValidator.java index a3a666a..962f530 100644 --- a/luni/src/main/java/java/security/cert/CertPathValidator.java +++ b/luni/src/main/java/java/security/cert/CertPathValidator.java @@ -140,7 +140,8 @@ public class CertPathValidator { /** * Returns a new certification path validator for the specified algorithm - * from the specified provider. + * from the specified provider. The {@code provider} supplied does not + * have to be registered. * * @param algorithm * the algorithm name. diff --git a/luni/src/main/java/java/security/cert/CertStore.java b/luni/src/main/java/java/security/cert/CertStore.java index 72d356f..606c93b 100644 --- a/luni/src/main/java/java/security/cert/CertStore.java +++ b/luni/src/main/java/java/security/cert/CertStore.java @@ -151,7 +151,9 @@ public class CertStore { /** * Creates a new {@code CertStore} instance from the specified provider with - * the specified type and initialized with the specified parameters. + * the specified type and initialized with the specified parameters. The + * {@code provider} supplied does not have to be registered. + * * @param type * the certificate store type. * @param params diff --git a/luni/src/main/java/java/security/cert/Certificate.java b/luni/src/main/java/java/security/cert/Certificate.java index 15c1dbe..675cf9f 100644 --- a/luni/src/main/java/java/security/cert/Certificate.java +++ b/luni/src/main/java/java/security/cert/Certificate.java @@ -152,15 +152,15 @@ public abstract class Certificate implements Serializable { * performed. * @param sigProvider * String the name of the signature provider. - * @exception CertificateException + * @throws CertificateException * if encoding errors are detected. - * @exception NoSuchAlgorithmException + * @throws NoSuchAlgorithmException * if an unsupported algorithm is detected. - * @exception InvalidKeyException + * @throws InvalidKeyException * if an invalid key is detected. - * @exception NoSuchProviderException + * @throws NoSuchProviderException * if the specified provider does not exists. - * @exception SignatureException + * @throws SignatureException * if signature errors are detected. */ public abstract void verify(PublicKey key, String sigProvider) diff --git a/luni/src/main/java/java/security/cert/CertificateFactory.java b/luni/src/main/java/java/security/cert/CertificateFactory.java index f882d52..2805c0e 100644 --- a/luni/src/main/java/java/security/cert/CertificateFactory.java +++ b/luni/src/main/java/java/security/cert/CertificateFactory.java @@ -128,7 +128,8 @@ public class CertificateFactory { /** * Creates a new {@code CertificateFactory} instance from the specified - * provider that provides the requested certificate type. + * provider that provides the requested certificate type. The + * {@code provider} supplied does not have to be registered. * * @param type * the certificate type. @@ -280,7 +281,7 @@ public class CertificateFactory { * @param inStream * the stream from where data is read to create the CRL. * @return an initialized CRL. - * @exception CRLException + * @throws CRLException * if parsing problems are detected. */ public final CRL generateCRL(InputStream inStream) throws CRLException { @@ -294,7 +295,7 @@ public class CertificateFactory { * @param inStream * the stream from which the data is read to create the CRLs. * @return an initialized collection of CRLs. - * @exception CRLException + * @throws CRLException * if parsing problems are detected. */ public final Collection<? extends CRL> generateCRLs(InputStream inStream) diff --git a/luni/src/main/java/java/security/cert/CertificateFactorySpi.java b/luni/src/main/java/java/security/cert/CertificateFactorySpi.java index 117ef65..d157d9c 100644 --- a/luni/src/main/java/java/security/cert/CertificateFactorySpi.java +++ b/luni/src/main/java/java/security/cert/CertificateFactorySpi.java @@ -44,7 +44,7 @@ public abstract class CertificateFactorySpi { * the stream from which the data is read to create the * certificate. * @return an initialized certificate. - * @exception CertificateException + * @throws CertificateException * if parsing problems are detected. */ public abstract Certificate engineGenerateCertificate(InputStream inStream) @@ -57,7 +57,7 @@ public abstract class CertificateFactorySpi { * @param inStream * the stream from where data is read to create the certificates. * @return a collection of certificates. - * @exception CertificateException + * @throws CertificateException * if parsing problems are detected. */ public abstract Collection<? extends Certificate> @@ -70,7 +70,7 @@ public abstract class CertificateFactorySpi { * @param inStream * the stream from where data is read to create the CRL. * @return an CRL instance. - * @exception CRLException + * @throws CRLException * if parsing problems are detected. */ public abstract CRL engineGenerateCRL(InputStream inStream) @@ -83,7 +83,7 @@ public abstract class CertificateFactorySpi { * @param inStream * the stream from which the data is read to create the CRLs. * @return a collection of CRLs. - * @exception CRLException + * @throws CRLException * if parsing problems are detected. */ public abstract Collection<? extends CRL> diff --git a/luni/src/main/java/java/security/cert/CertificateRevokedException.java b/luni/src/main/java/java/security/cert/CertificateRevokedException.java new file mode 100644 index 0000000..4d88a4d --- /dev/null +++ b/luni/src/main/java/java/security/cert/CertificateRevokedException.java @@ -0,0 +1,155 @@ +/* + * Copyright 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 java.security.cert; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.security.auth.x500.X500Principal; +import org.apache.harmony.security.x509.InvalidityDate; + +/** + * Exception that is thrown when a certificate is invalid because it has been + * revoked. + * + * @since 1.7 + * @hide + */ +public class CertificateRevokedException extends CertificateException { + + private static final long serialVersionUID = 7839996631571608627L; + + private final Date revocationDate; + + private final CRLReason reason; + + private final X500Principal authority; + + // Maps may not be serializable, so serialize it manually. + private transient Map<String, Extension> extensions; + + /** + * @param revocationDate date the certificate was revoked + * @param reason reason the certificate was revoked if available + * @param authority authority that revoked the certificate + * @param extensions X.509 extensions associated with this revocation + */ + public CertificateRevokedException(Date revocationDate, CRLReason reason, + X500Principal authority, Map<String, Extension> extensions) { + this.revocationDate = revocationDate; + this.reason = reason; + this.authority = authority; + this.extensions = extensions; + } + + /** + * Returns the principal of the authority that issued the revocation. + */ + public X500Principal getAuthorityName() { + return authority; + } + + /** + * X.509 extensions that are associated with this revocation. + */ + public Map<String, Extension> getExtensions() { + return Collections.unmodifiableMap(extensions); + } + + /** + * Returns the date when the certificate was known to become invalid if + * available. + */ + public Date getInvalidityDate() { + if (extensions == null) { + return null; + } + + Extension invalidityDateExtension = extensions.get("2.5.29.24"); + if (invalidityDateExtension == null) { + return null; + } + + try { + InvalidityDate invalidityDate = new InvalidityDate(invalidityDateExtension.getValue()); + return invalidityDate.getDate(); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the detail message of the thrown exception. + */ + public String getMessage() { + StringBuffer sb = new StringBuffer("Certificate was revoked"); + if (revocationDate != null) { + sb.append(" on ").append(revocationDate.toString()); + } + if (reason != null) { + sb.append(" due to ").append(reason); + } + return sb.toString(); + } + + /** + * Returns the date the certificate was revoked. + */ + public Date getRevocationDate() { + return (Date) revocationDate.clone(); + } + + /** + * Returns the reason the certificate was revoked if available. + */ + public CRLReason getRevocationReason() { + return reason; + } + + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + + int size = stream.readInt(); + extensions = new HashMap<String, Extension>(size); + for (int i = 0; i < size; i++) { + String oid = (String) stream.readObject(); + boolean critical = stream.readBoolean(); + int valueLen = stream.readInt(); + byte[] value = new byte[valueLen]; + stream.read(value); + extensions.put(oid, + new org.apache.harmony.security.x509.Extension(oid, critical, value)); + } + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + + stream.writeInt(extensions.size()); + for (Extension e : extensions.values()) { + stream.writeObject(e.getId()); + stream.writeBoolean(e.isCritical()); + byte[] value = e.getValue(); + stream.writeInt(value.length); + stream.write(value); + } + } +} diff --git a/luni/src/main/java/java/security/cert/Extension.java b/luni/src/main/java/java/security/cert/Extension.java new file mode 100644 index 0000000..8013809 --- /dev/null +++ b/luni/src/main/java/java/security/cert/Extension.java @@ -0,0 +1,65 @@ +/* + * Copyright 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 java.security.cert; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * The Extension part of an X.509 certificate (as specified in <a + * href="http://www.ietf.org/rfc/rfc3280.txt">RFC 3280 — Internet X.509 + * Public Key Infrastructure: Certificate and Certificate Revocation List (CRL) + * Profile</p>): + * + * <pre> + * Extension ::= SEQUENCE { + * extnID OBJECT IDENTIFIER, + * critical BOOLEAN DEFAULT FALSE, + * extnValue OCTET STRING + * } + * </pre> + * + * @since 1.7 + * @hide + */ +public interface Extension { + /** + * Returns the OID (Object Identifier) for this extension encoded as a + * string (e.g., "2.5.29.15"). + */ + String getId(); + + /** + * Returns {@code true} if this extension is critical. If this is true and + * an implementation does not understand this extension, it must reject it. + * See RFC 3280 section 4.2 for more information. + */ + boolean isCritical(); + + /** + * The DER-encoded value of this extension. + */ + byte[] getValue(); + + /** + * Writes the DER-encoded extension to {@code out}. + * + * @throws IOException when there is an encoding error or error writing to + * {@code out} + */ + void encode(OutputStream out) throws IOException; +} diff --git a/luni/src/main/java/java/security/cert/X509CRLEntry.java b/luni/src/main/java/java/security/cert/X509CRLEntry.java index 4b4c15d..451cfd5 100644 --- a/luni/src/main/java/java/security/cert/X509CRLEntry.java +++ b/luni/src/main/java/java/security/cert/X509CRLEntry.java @@ -17,10 +17,13 @@ package java.security.cert; +import java.io.IOException; import java.math.BigInteger; import java.util.Arrays; import java.util.Date; import javax.security.auth.x500.X500Principal; +import org.apache.harmony.security.asn1.ASN1OctetString; +import org.apache.harmony.security.x509.ReasonCode; /** * Abstract base class for entries in a certificate revocation list (CRL). @@ -121,5 +124,25 @@ public abstract class X509CRLEntry implements X509Extension { * @return a string representation of this instance. */ public abstract String toString(); -} + /** + * Returns the reason this CRL entry was revoked. If the implementation + * doesn't support reasons, this will return {@code null}. + * + * @since 1.7 + * @hide + */ + public CRLReason getRevocationReason() { + byte[] reasonBytes = getExtensionValue("2.5.29.21"); + if (reasonBytes == null) { + return null; + } + + try { + byte[] rawBytes = (byte[]) ASN1OctetString.getInstance().decode(reasonBytes); + return new ReasonCode(rawBytes).getReason(); + } catch (IOException e) { + return null; + } + } +}
\ No newline at end of file diff --git a/luni/src/main/java/java/security/interfaces/DSAParams.java b/luni/src/main/java/java/security/interfaces/DSAParams.java index ed1fcb5..64ddb01 100644 --- a/luni/src/main/java/java/security/interfaces/DSAParams.java +++ b/luni/src/main/java/java/security/interfaces/DSAParams.java @@ -39,9 +39,9 @@ public interface DSAParams { public BigInteger getP(); /** - * Returns the subprime ({@code q} value. + * Returns the subprime ({@code q}) value. * - * @return the subprime ({@code q} value. + * @return the subprime ({@code q}) value. */ public BigInteger getQ(); diff --git a/luni/src/main/java/java/security/security.properties b/luni/src/main/java/java/security/security.properties index dd5830d..a06283b 100644 --- a/luni/src/main/java/java/security/security.properties +++ b/luni/src/main/java/java/security/security.properties @@ -20,13 +20,11 @@ # # Android's provider of OpenSSL backed implementations security.provider.1=com.android.org.conscrypt.OpenSSLProvider -# Favor Harmony's CertificateFactory.X509 over BouncyCastle's -security.provider.2=org.apache.harmony.security.provider.cert.DRLCertFactory # Android's stripped down BouncyCastle provider -security.provider.3=com.android.org.bouncycastle.jce.provider.BouncyCastleProvider +security.provider.2=com.android.org.bouncycastle.jce.provider.BouncyCastleProvider # Remaining Harmony providers -security.provider.4=org.apache.harmony.security.provider.crypto.CryptoProvider -security.provider.5=com.android.org.conscrypt.JSSEProvider +security.provider.3=org.apache.harmony.security.provider.crypto.CryptoProvider +security.provider.4=com.android.org.conscrypt.JSSEProvider diff --git a/luni/src/main/java/java/security/spec/ECParameterSpec.java b/luni/src/main/java/java/security/spec/ECParameterSpec.java index 9860ac0..37b39ac 100644 --- a/luni/src/main/java/java/security/spec/ECParameterSpec.java +++ b/luni/src/main/java/java/security/spec/ECParameterSpec.java @@ -32,7 +32,7 @@ public class ECParameterSpec implements AlgorithmParameterSpec { // Cofactor private final int cofactor; // Name of curve if available. - private final String curveName; + private String curveName; /** * Creates a new {@code ECParameterSpec} with the specified elliptic curve, @@ -52,23 +52,10 @@ public class ECParameterSpec implements AlgorithmParameterSpec { */ public ECParameterSpec(EllipticCurve curve, ECPoint generator, BigInteger order, int cofactor) { - this(curve, generator, order, cofactor, null); - } - - /** - * Creates a new {@code ECParameterSpec} with the specified named curve - * and all of its parameters. - * - * @see #ECParameterSpec(EllipticCurve, ECPoint, BigInteger, int) - * @hide - */ - public ECParameterSpec(EllipticCurve curve, ECPoint generator, - BigInteger order, int cofactor, String curveName) { this.curve = curve; this.generator = generator; this.order = order; this.cofactor = cofactor; - this.curveName = curveName; // throw NullPointerException if curve, generator or order is null if (this.curve == null) { throw new NullPointerException("curve == null"); @@ -125,6 +112,15 @@ public class ECParameterSpec implements AlgorithmParameterSpec { } /** + * Used to set the curve name if available. + * + * @hide + */ + public void setCurveName(String curveName) { + this.curveName = curveName; + } + + /** * Returns the name of the curve if this is a named curve. Returns * {@code null} if this is not known to be a named curve. * diff --git a/luni/src/main/java/java/sql/Date.java b/luni/src/main/java/java/sql/Date.java index 4aac326..206d885 100644 --- a/luni/src/main/java/java/sql/Date.java +++ b/luni/src/main/java/java/sql/Date.java @@ -214,23 +214,19 @@ public class Date extends java.util.Date { if (dateString == null) { throw new IllegalArgumentException("dateString == null"); } - int firstIndex = dateString.indexOf('-'); - int secondIndex = dateString.indexOf('-', firstIndex + 1); - // secondIndex == -1 means none or only one separator '-' has been - // found. - // The string is separated into three parts by two separator characters, - // if the first or the third part is null string, we should throw - // IllegalArgumentException to follow RI - if (secondIndex == -1 || firstIndex == 0 - || secondIndex + 1 == dateString.length()) { + if (dateString.length() > 10) { + // early fail to avoid parsing huge invalid strings throw new IllegalArgumentException(); } - // parse each part of the string - int year = Integer.parseInt(dateString.substring(0, firstIndex)); - int month = Integer.parseInt(dateString.substring(firstIndex + 1, - secondIndex)); - int day = Integer.parseInt(dateString.substring(secondIndex + 1, - dateString.length())); + + String[] parts = dateString.split("-"); + if (parts.length != 3) { + throw new IllegalArgumentException(); + } + + int year = Integer.parsePositiveInt(parts[0]); + int month = Integer.parsePositiveInt(parts[1]); + int day = Integer.parsePositiveInt(parts[2]); return new Date(year - 1900, month - 1, day); } diff --git a/luni/src/main/java/java/sql/Timestamp.java b/luni/src/main/java/java/sql/Timestamp.java index f35fa9b..a45a2b8 100644 --- a/luni/src/main/java/java/sql/Timestamp.java +++ b/luni/src/main/java/java/sql/Timestamp.java @@ -408,7 +408,7 @@ public class Timestamp extends Date { throw new IllegalArgumentException("Argument cannot be null"); } - // omit trailing whitespace + // Omit trailing whitespace s = s.trim(); if (!Pattern.matches(TIME_FORMAT_REGEX, s)) { throw badTimestampString(s); @@ -424,14 +424,14 @@ public class Timestamp extends Date { * with the ParsePosition indicating the index of the "." which should * precede the nanoseconds value */ - Date theDate; + Date date; try { - theDate = df.parse(s, pp); + date = df.parse(s, pp); } catch (Exception e) { throw badTimestampString(s); } - if (theDate == null) { + if (date == null) { throw badTimestampString(s); } @@ -445,66 +445,38 @@ public class Timestamp extends Date { */ int position = pp.getIndex(); int remaining = s.length() - position; - int theNanos; + int nanos; if (remaining == 0) { // First, allow for the case where no fraction of a second is given: - theNanos = 0; + nanos = 0; } else { - /* - * Case where fraction of a second is specified: Require 1 character - * plus the "." in the remaining part of the string... - */ - if ((s.length() - position) < ".n".length()) { + // Validate the string is in the range ".0" to ".999999999" + if (remaining < 2 || remaining > 10 || s.charAt(position) != '.') { throw badTimestampString(s); } - - /* - * If we're strict, we should not allow any EXTRA characters after - * the 9 digits - */ - if ((s.length() - position) > ".nnnnnnnnn".length()) { - throw badTimestampString(s); - } - - // Require the next character to be a "." - if (s.charAt(position) != '.') { - throw new NumberFormatException("Bad input string format: expected '.' not '" + - s.charAt(position) + "' in \"" + s + "\""); - } - // Get the length of the number string - need to account for the '.' - int nanoLength = s.length() - position - 1; - - // Get the 9 characters following the "." as an integer - String theNanoString = s.substring(position + 1, position + 1 - + nanoLength); - /* - * We must adjust for the cases where the nanos String was not 9 - * characters long by padding out with zeros - */ - theNanoString = theNanoString + "000000000"; - theNanoString = theNanoString.substring(0, 9); - try { - theNanos = Integer.parseInt(theNanoString); - } catch (Exception e) { - // If we get here, the string was not a number + nanos = Integer.parsePositiveInt(s.substring(position + 1)); + } catch (NumberFormatException e) { throw badTimestampString(s); } + // We must adjust for the cases where the nanos String was not 9 + // characters long (i.e. ".123" means 123000000 nanos) + if (nanos != 0) { + for (int i = remaining - 1; i < 9; i++) { + nanos *= 10; + } + } } - if (theNanos < 0 || theNanos > 999999999) { - throw badTimestampString(s); - } - - Timestamp theTimestamp = new Timestamp(theDate.getTime()); - theTimestamp.setNanos(theNanos); + Timestamp timestamp = new Timestamp(date.getTime()); + timestamp.setNanos(nanos); - return theTimestamp; + return timestamp; } private static IllegalArgumentException badTimestampString(String s) { - throw new IllegalArgumentException("Timestamp format must be " + + return new IllegalArgumentException("Timestamp format must be " + "yyyy-MM-dd HH:mm:ss.fffffffff; was '" + s + "'"); } } diff --git a/luni/src/main/java/java/text/Bidi.java b/luni/src/main/java/java/text/Bidi.java index e4b30e6..d73ea4a 100644 --- a/luni/src/main/java/java/text/Bidi.java +++ b/luni/src/main/java/java/text/Bidi.java @@ -421,28 +421,20 @@ public final class Bidi { /** * Returns the base level. - * - * @return the base level. */ public int getBaseLevel() { return baseLevel; } /** - * Returns the length of the text in the {@code Bidi} object. - * - * @return the length. + * Returns the length of the text. */ public int getLength() { return length; } /** - * Returns the level of a specified character. - * - * @param offset - * the offset of the character. - * @return the level. + * Returns the level of the given character. */ public int getLevelAt(int offset) { try { @@ -453,74 +445,51 @@ public final class Bidi { } /** - * Returns the number of runs in the bidirectional text. - * - * @return the number of runs, at least 1. + * Returns the number of runs in the text, at least 1. */ public int getRunCount() { return unidirectional ? 1 : runs.length; } /** - * Returns the level of the specified run. - * - * @param run - * the index of the run. - * @return the level of the run. + * Returns the level of the given run. */ public int getRunLevel(int run) { return unidirectional ? baseLevel : runs[run].getLevel(); } /** - * Returns the limit offset of the specified run. - * - * @param run - * the index of the run. - * @return the limit offset of the run. + * Returns the limit offset of the given run. */ public int getRunLimit(int run) { return unidirectional ? length : runs[run].getLimit(); } /** - * Returns the start offset of the specified run. - * - * @param run - * the index of the run. - * @return the start offset of the run. + * Returns the start offset of the given run. */ public int getRunStart(int run) { return unidirectional ? 0 : runs[run].getStart(); } /** - * Indicates whether the text is from left to right, that is, both the base + * Returns true if the text is from left to right, that is, both the base * direction and the text direction is from left to right. - * - * @return {@code true} if the text is from left to right; {@code false} - * otherwise. */ public boolean isLeftToRight() { return direction == UBiDiDirection_UBIDI_LTR; } /** - * Indicates whether the text direction is mixed. - * - * @return {@code true} if the text direction is mixed; {@code false} - * otherwise. + * Returns true if the text direction is mixed. */ public boolean isMixed() { return direction == UBiDiDirection_UBIDI_MIXED; } /** - * Indicates whether the text is from right to left, that is, both the base + * Returns true if the text is from right to left, that is, both the base * direction and the text direction is from right to left. - * - * @return {@code true} if the text is from right to left; {@code false} - * otherwise. */ public boolean isRightToLeft() { return direction == UBiDiDirection_UBIDI_RTL; diff --git a/luni/src/main/java/java/text/BreakIterator.java b/luni/src/main/java/java/text/BreakIterator.java index b14647c..81545b2 100644 --- a/luni/src/main/java/java/text/BreakIterator.java +++ b/luni/src/main/java/java/text/BreakIterator.java @@ -268,13 +268,9 @@ public abstract class BreakIterator implements Cloneable { /** * Returns a new instance of {@code BreakIterator} to iterate over * characters using the given locale. - * - * @param where - * the given locale. - * @return a new instance of {@code BreakIterator} using the given locale. */ - public static BreakIterator getCharacterInstance(Locale where) { - return new RuleBasedBreakIterator(NativeBreakIterator.getCharacterInstance(where)); + public static BreakIterator getCharacterInstance(Locale locale) { + return new RuleBasedBreakIterator(NativeBreakIterator.getCharacterInstance(locale)); } /** @@ -290,14 +286,9 @@ public abstract class BreakIterator implements Cloneable { /** * Returns a new instance of {@code BreakIterator} to iterate over * line breaks using the given locale. - * - * @param where - * the given locale. - * @return a new instance of {@code BreakIterator} using the given locale. - * @throws NullPointerException if {@code where} is {@code null}. */ - public static BreakIterator getLineInstance(Locale where) { - return new RuleBasedBreakIterator(NativeBreakIterator.getLineInstance(where)); + public static BreakIterator getLineInstance(Locale locale) { + return new RuleBasedBreakIterator(NativeBreakIterator.getLineInstance(locale)); } /** @@ -313,14 +304,9 @@ public abstract class BreakIterator implements Cloneable { /** * Returns a new instance of {@code BreakIterator} to iterate over * sentence-breaks using the given locale. - * - * @param where - * the given locale. - * @return a new instance of {@code BreakIterator} using the given locale. - * @throws NullPointerException if {@code where} is {@code null}. */ - public static BreakIterator getSentenceInstance(Locale where) { - return new RuleBasedBreakIterator(NativeBreakIterator.getSentenceInstance(where)); + public static BreakIterator getSentenceInstance(Locale locale) { + return new RuleBasedBreakIterator(NativeBreakIterator.getSentenceInstance(locale)); } /** @@ -336,14 +322,9 @@ public abstract class BreakIterator implements Cloneable { /** * Returns a new instance of {@code BreakIterator} to iterate over * word-breaks using the given locale. - * - * @param where - * the given locale. - * @return a new instance of {@code BreakIterator} using the given locale. - * @throws NullPointerException if {@code where} is {@code null}. */ - public static BreakIterator getWordInstance(Locale where) { - return new RuleBasedBreakIterator(NativeBreakIterator.getWordInstance(where)); + public static BreakIterator getWordInstance(Locale locale) { + return new RuleBasedBreakIterator(NativeBreakIterator.getWordInstance(locale)); } /** diff --git a/luni/src/main/java/java/text/CollationElementIterator.java b/luni/src/main/java/java/text/CollationElementIterator.java index 5da3b65..4f75a9a 100644 --- a/luni/src/main/java/java/text/CollationElementIterator.java +++ b/luni/src/main/java/java/text/CollationElementIterator.java @@ -43,6 +43,11 @@ import libcore.icu.CollationElementIteratorICU; * "\u0086b": the first collation element is collation_element('a'), the * second one is collation_element('e'), and the third collation element is * collation_element('b'). + * + * <p>Note that calls to {@code next} and {@code previous} can not be mixed. + * To change iteration direction, {@code reset}, {@code setOffset} or {@code setText} + * must be called to reset the iterator. If a change of direction is done without one + * of these calls, the result is undefined. */ public final class CollationElementIterator { @@ -61,7 +66,7 @@ public final class CollationElementIterator { } /** - * Obtains the maximum length of any expansion sequence that ends with the + * Returns the maximum length of any expansion sequence that ends with the * specified collation element. Returns {@code 1} if there is no expansion * with this collation element as the last element. * @@ -69,15 +74,13 @@ public final class CollationElementIterator { * a collation element that has been previously obtained from a * call to either the {@link #next()} or {@link #previous()} * method. - * @return the maximum length of any expansion sequence ending with the - * specified collation element. */ public int getMaxExpansion(int order) { return this.icuIterator.getMaxExpansion(order); } /** - * Obtains the character offset in the source string corresponding to the + * Returns the character offset in the source string corresponding to the * next collation element. This value could be any of: * <ul> * <li>The index of the first character in the source string that matches @@ -94,42 +97,33 @@ public final class CollationElementIterator { * <li>The length of the source string, if iteration has reached the end. * </li> * </ul> - * - * @return The position of the collation element in the source string that - * will be returned by the next invocation of the {@link #next()} - * method. */ public int getOffset() { return this.icuIterator.getOffset(); } /** - * Obtains the next collation element in the source string. - * - * @return the next collation element or {@code NULLORDER} if the end - * of the iteration has been reached. + * Returns the next collation element in the source string or {@code NULLORDER} if + * the end of the iteration has been reached. */ public int next() { return this.icuIterator.next(); } /** - * Obtains the previous collation element in the source string. - * - * @return the previous collation element, or {@code NULLORDER} when - * the start of the iteration has been reached. + * Returns the previous collation element in the source string or {@code NULLORDER} if + * the start of the iteration has been reached. */ public int previous() { return this.icuIterator.previous(); } /** - * Obtains the primary order of the specified collation element, i.e. the + * Returns the primary order of the specified collation element, i.e. the * first 16 bits. This value is unsigned. * * @param order * the element of the collation. - * @return the element's 16 bit primary order. */ public static final int primaryOrder(int order) { return CollationElementIteratorICU.primaryOrder(order); @@ -149,12 +143,11 @@ public final class CollationElementIterator { } /** - * Obtains the secondary order of the specified collation element, i.e. the + * Returns the secondary order of the specified collation element, i.e. the * 16th to 23th bits, inclusive. This value is unsigned. * * @param order * the element of the collator. - * @return the 8 bit secondary order of the element. */ public static final short secondaryOrder(int order) { return (short) CollationElementIteratorICU.secondaryOrder(order); @@ -209,12 +202,11 @@ public final class CollationElementIterator { } /** - * Obtains the tertiary order of the specified collation element, i.e. the + * Returns the tertiary order of the specified collation element, i.e. the * last 8 bits. This value is unsigned. * * @param order * the element of the collation. - * @return the 8 bit tertiary order of the element. */ public static final short tertiaryOrder(int order) { return (short) CollationElementIteratorICU.tertiaryOrder(order); diff --git a/luni/src/main/java/java/text/DateFormat.java b/luni/src/main/java/java/text/DateFormat.java index ac64eed..3055843 100644 --- a/luni/src/main/java/java/text/DateFormat.java +++ b/luni/src/main/java/java/text/DateFormat.java @@ -388,6 +388,9 @@ public abstract class DateFormat extends Format { */ public static final DateFormat getDateInstance(int style, Locale locale) { checkDateStyle(style); + if (locale == null) { + throw new NullPointerException("locale == null"); + } return new SimpleDateFormat(LocaleData.get(locale).getDateFormat(style), locale); } @@ -440,6 +443,9 @@ public abstract class DateFormat extends Format { public static final DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) { checkTimeStyle(timeStyle); checkDateStyle(dateStyle); + if (locale == null) { + throw new NullPointerException("locale == null"); + } LocaleData localeData = LocaleData.get(locale); String pattern = localeData.getDateFormat(dateStyle) + " " + localeData.getTimeFormat(timeStyle); return new SimpleDateFormat(pattern, locale); @@ -457,6 +463,12 @@ public abstract class DateFormat extends Format { } /** + * @hide for internal use only. + */ + public static final void set24HourTimePref(boolean is24Hour) { + } + + /** * Returns the {@code NumberFormat} used by this {@code DateFormat}. * * @return the {@code NumberFormat} used by this date format. @@ -508,6 +520,10 @@ public abstract class DateFormat extends Format { */ public static final DateFormat getTimeInstance(int style, Locale locale) { checkTimeStyle(style); + if (locale == null) { + throw new NullPointerException("locale == null"); + } + return new SimpleDateFormat(LocaleData.get(locale).getTimeFormat(style), locale); } diff --git a/luni/src/main/java/java/text/DateFormatSymbols.java b/luni/src/main/java/java/text/DateFormatSymbols.java index e75b82c..ee34bbd 100644 --- a/luni/src/main/java/java/text/DateFormatSymbols.java +++ b/luni/src/main/java/java/text/DateFormatSymbols.java @@ -62,16 +62,14 @@ public class DateFormatSymbols implements Serializable, Cloneable { transient LocaleData localeData; // Localized display names. - String[][] zoneStrings; - // Has the user called setZoneStrings? - transient boolean customZoneStrings; + private String[][] zoneStrings; - /** - * Locale, necessary to lazily load time zone strings. We force the time - * zone names to load upon serialization, so this will never be needed - * post deserialization. + /* + * Locale, necessary to lazily load time zone strings. Added to the serialized form for + * Android's L release. May be null if the object was deserialized using data from older + * releases. See b/16502916. */ - transient final Locale locale; + private final Locale locale; /** * Gets zone strings, initializing them if necessary. Does not create @@ -102,7 +100,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { * the locale. */ public DateFormatSymbols(Locale locale) { - this.locale = locale; + this.locale = LocaleData.mapInvalidAndNullLocales(locale); this.localPatternChars = SimpleDateFormat.PATTERN_CHARS; this.localeData = LocaleData.get(locale); @@ -152,6 +150,12 @@ public class DateFormatSymbols implements Serializable, Cloneable { private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); + + Locale locale = this.locale; + if (locale == null) { + // Before the L release Android did not serialize the locale. Handle its absence. + locale = Locale.getDefault(); + } this.localeData = LocaleData.get(locale); } @@ -215,7 +219,6 @@ public class DateFormatSymbols implements Serializable, Cloneable { // 'zoneStrings' is so large, we just print a representative value. return getClass().getName() + "[amPmStrings=" + Arrays.toString(ampms) + - ",customZoneStrings=" + customZoneStrings + ",eras=" + Arrays.toString(eras) + ",localPatternChars=" + localPatternChars + ",months=" + Arrays.toString(months) + @@ -251,8 +254,6 @@ public class DateFormatSymbols implements Serializable, Cloneable { /** * Returns the pattern characters used by {@link SimpleDateFormat} to * specify date and time fields. - * - * @return a string containing the pattern characters. */ public String getLocalPatternChars() { return localPatternChars; @@ -485,6 +486,25 @@ public class DateFormatSymbols implements Serializable, Cloneable { } } this.zoneStrings = clone2dStringArray(zoneStrings); - this.customZoneStrings = true; + } + + /** + * Returns the display name of the timezone specified. Returns null if no name was found in the + * zone strings. + * + * @param daylight whether to return the daylight savings or the standard name + * @param style one of the {@link TimeZone} styles such as {@link TimeZone#SHORT} + * + * @hide used internally + */ + String getTimeZoneDisplayName(TimeZone tz, boolean daylight, int style) { + if (style != TimeZone.SHORT && style != TimeZone.LONG) { + throw new IllegalArgumentException("Bad style: " + style); + } + + // If custom zone strings have been set with setZoneStrings() we use those. Otherwise we + // use the ones from LocaleData. + String[][] zoneStrings = internalZoneStrings(); + return TimeZoneNames.getDisplayName(zoneStrings, tz.getID(), daylight, style); } } diff --git a/luni/src/main/java/java/text/DecimalFormat.java b/luni/src/main/java/java/text/DecimalFormat.java index 948bec1..554fd74 100644 --- a/luni/src/main/java/java/text/DecimalFormat.java +++ b/luni/src/main/java/java/text/DecimalFormat.java @@ -373,11 +373,11 @@ import libcore.icu.NativeDecimalFormat; * digits is fixed at one and there is no exponent grouping. * <li>Exponential patterns may not contain grouping separators. * </ul> - * <a name="sigdig"> + * <a name="sigdig"></a> * <h4> <strong><font color="red">NEW</font> </strong> Significant * Digits</h4> * <p> - * </a> {@code DecimalFormat} has two ways of controlling how many digits are + * {@code DecimalFormat} has two ways of controlling how many digits are * shown: (a) significant digit counts or (b) integer and fraction digit counts. * Integer and fraction digit counts are described above. When a formatter uses * significant digits counts, the number of integer and fraction digits is not @@ -487,6 +487,12 @@ import libcore.icu.NativeDecimalFormat; * <em>escapes</em> the following character. If there is no character after * the pad escape, then the pattern is illegal.</li> * </ul> + * + * <h4>Serialization</h4> + * <p> + * Features marked as <strong><font color="red">NEW</font></strong> and patterns that use + * characters not documented above are unlikely to serialize/deserialize correctly. + * * <h4>Synchronization</h4> * <p> * {@code DecimalFormat} objects are not synchronized. Multiple threads should @@ -574,6 +580,7 @@ public class DecimalFormat extends NumberFormat { */ public void applyLocalizedPattern(String pattern) { ndf.applyLocalizedPattern(pattern); + updateFieldsFromNative(); } /** @@ -586,7 +593,18 @@ public class DecimalFormat extends NumberFormat { * if the pattern cannot be parsed. */ public void applyPattern(String pattern) { + // The underlying ICU4C accepts a super-set of the pattern spec documented by the Android + // APIs. For example, rounding increments (pattern characters '1'-'9'). They will work but + // see class doc for issues with serialization/deserialization they may cause. ndf.applyPattern(pattern); + updateFieldsFromNative(); + } + + private void updateFieldsFromNative() { + maximumIntegerDigits = ndf.getMaximumIntegerDigits(); + minimumIntegerDigits = ndf.getMinimumIntegerDigits(); + maximumFractionDigits = ndf.getMaximumFractionDigits(); + minimumFractionDigits = ndf.getMinimumFractionDigits(); } /** @@ -659,21 +677,6 @@ public class DecimalFormat extends NumberFormat { @Override public StringBuffer format(double value, StringBuffer buffer, FieldPosition position) { checkBufferAndFieldPosition(buffer, position); - // All float/double/Float/Double formatting ends up here... - if (roundingMode == RoundingMode.UNNECESSARY) { - // ICU4C doesn't support this rounding mode, so we have to fake it. - try { - setRoundingMode(RoundingMode.UP); - String upResult = format(value, new StringBuffer(), new FieldPosition(0)).toString(); - setRoundingMode(RoundingMode.DOWN); - String downResult = format(value, new StringBuffer(), new FieldPosition(0)).toString(); - if (!upResult.equals(downResult)) { - throw new ArithmeticException("rounding mode UNNECESSARY but rounding required"); - } - } finally { - setRoundingMode(RoundingMode.UNNECESSARY); - } - } buffer.append(ndf.formatDouble(value, position)); return buffer; } @@ -735,16 +738,6 @@ public class DecimalFormat extends NumberFormat { } /** - * Returns the multiplier which is applied to the number before formatting - * or after parsing. - * - * @return the multiplier. - */ - public int getMultiplier() { - return ndf.getMultiplier(); - } - - /** * Returns the prefix which is formatted or parsed before a negative number. * * @return the negative prefix. @@ -826,16 +819,10 @@ public class DecimalFormat extends NumberFormat { // In this implementation, NativeDecimalFormat is wrapped to // fulfill most of the format and parse feature. And this method is // delegated to the wrapped instance of NativeDecimalFormat. + super.setParseIntegerOnly(value); ndf.setParseIntegerOnly(value); } - /** - * Indicates whether parsing with this decimal format will only - * return numbers of type {@code java.lang.Integer}. - * - * @return {@code true} if this {@code DecimalFormat}'s parse method only - * returns {@code java.lang.Integer}; {@code false} otherwise. - */ @Override public boolean isParseIntegerOnly() { return ndf.isParseIntegerOnly(); @@ -898,9 +885,6 @@ public class DecimalFormat extends NumberFormat { /** * Sets the {@code DecimalFormatSymbols} used by this decimal format. - * - * @param value - * the {@code DecimalFormatSymbols} to set. */ public void setDecimalFormatSymbols(DecimalFormatSymbols value) { if (value != null) { @@ -913,24 +897,17 @@ public class DecimalFormat extends NumberFormat { /** * Sets the currency used by this decimal format. The min and max fraction * digits remain the same. - * - * @param currency - * the currency this {@code DecimalFormat} should use. - * @see DecimalFormatSymbols#setCurrency(Currency) */ @Override public void setCurrency(Currency currency) { - ndf.setCurrency(Currency.getInstance(currency.getCurrencyCode())); - symbols.setCurrency(currency); + Currency instance = Currency.getInstance(currency.getCurrencyCode()); + symbols.setCurrency(instance); + ndf.setCurrency(symbols.getCurrencySymbol(), currency.getCurrencyCode()); } /** - * Sets whether the decimal separator is shown when there are no fractional + * Sets whether the decimal separator is shown even when there are no fractional * digits. - * - * @param value - * {@code true} if the decimal separator should always be - * formatted; {@code false} otherwise. */ public void setDecimalSeparatorAlwaysShown(boolean value) { ndf.setDecimalSeparatorAlwaysShown(value); @@ -940,20 +917,14 @@ public class DecimalFormat extends NumberFormat { * Sets the number of digits grouped together by the grouping separator. * This only allows to set the primary grouping size; the secondary grouping * size can only be set with a pattern. - * - * @param value - * the number of digits grouped together. */ public void setGroupingSize(int value) { ndf.setGroupingSize(value); } /** - * Sets whether or not grouping will be used in this format. Grouping - * affects both parsing and formatting. - * - * @param value - * {@code true} if grouping is used; {@code false} otherwise. + * Sets whether or not digit grouping will be used in this format. Grouping + * affects both formatting and parsing. */ @Override public void setGroupingUsed(boolean value) { @@ -961,9 +932,8 @@ public class DecimalFormat extends NumberFormat { } /** - * Indicates whether grouping will be used in this format. - * - * @return {@code true} if grouping is used; {@code false} otherwise. + * Returns true if digit grouping is used in this format. Grouping affects both + * formatting and parsing. */ @Override public boolean isGroupingUsed() { @@ -974,8 +944,6 @@ public class DecimalFormat extends NumberFormat { * Sets the maximum number of digits after the decimal point. * If the value passed is negative then it is replaced by 0. * Regardless of this setting, no more than 340 digits will be used. - * - * @param value the maximum number of fraction digits. */ @Override public void setMaximumFractionDigits(int value) { @@ -989,8 +957,6 @@ public class DecimalFormat extends NumberFormat { * Sets the maximum number of digits before the decimal point. * If the value passed is negative then it is replaced by 0. * Regardless of this setting, no more than 309 digits will be used. - * - * @param value the maximum number of integer digits. */ @Override public void setMaximumIntegerDigits(int value) { @@ -1002,8 +968,6 @@ public class DecimalFormat extends NumberFormat { * Sets the minimum number of digits after the decimal point. * If the value passed is negative then it is replaced by 0. * Regardless of this setting, no more than 340 digits will be used. - * - * @param value the minimum number of fraction digits. */ @Override public void setMinimumFractionDigits(int value) { @@ -1015,8 +979,6 @@ public class DecimalFormat extends NumberFormat { * Sets the minimum number of digits before the decimal point. * If the value passed is negative then it is replaced by 0. * Regardless of this setting, no more than 309 digits will be used. - * - * @param value the minimum number of integer digits. */ @Override public void setMinimumIntegerDigits(int value) { @@ -1025,11 +987,20 @@ public class DecimalFormat extends NumberFormat { } /** + * Returns the multiplier which is applied to the number before formatting + * or after parsing. The multiplier is meant for tasks like parsing percentages. + * For example, given a multiplier of 100, 1.23 would be formatted as "123" and + * "123" would be parsed as 1.23. + */ + public int getMultiplier() { + return ndf.getMultiplier(); + } + + /** * Sets the multiplier which is applied to the number before formatting or - * after parsing. - * - * @param value - * the multiplier. + * after parsing. The multiplier meant for tasks like parsing percentages. + * For example, given a multiplier of 100, 1.23 would be formatted as "123" and + * "123" would be parsed as 1.23. */ public void setMultiplier(int value) { ndf.setMultiplier(value); @@ -1037,9 +1008,6 @@ public class DecimalFormat extends NumberFormat { /** * Sets the prefix which is formatted or parsed before a negative number. - * - * @param value - * the negative prefix. */ public void setNegativePrefix(String value) { ndf.setNegativePrefix(value); @@ -1047,9 +1015,6 @@ public class DecimalFormat extends NumberFormat { /** * Sets the suffix which is formatted or parsed after a negative number. - * - * @param value - * the negative suffix. */ public void setNegativeSuffix(String value) { ndf.setNegativeSuffix(value); @@ -1057,9 +1022,6 @@ public class DecimalFormat extends NumberFormat { /** * Sets the prefix which is formatted or parsed before a positive number. - * - * @param value - * the positive prefix. */ public void setPositivePrefix(String value) { ndf.setPositivePrefix(value); @@ -1067,9 +1029,6 @@ public class DecimalFormat extends NumberFormat { /** * Sets the suffix which is formatted or parsed after a positive number. - * - * @param value - * the positive suffix. */ public void setPositiveSuffix(String value) { ndf.setPositiveSuffix(value); @@ -1239,9 +1198,12 @@ public class DecimalFormat extends NumberFormat { throw new NullPointerException("roundingMode == null"); } this.roundingMode = roundingMode; - if (roundingMode != RoundingMode.UNNECESSARY) { // ICU4C doesn't support UNNECESSARY. - double roundingIncrement = 1.0 / Math.pow(10, Math.max(0, getMaximumFractionDigits())); - ndf.setRoundingMode(roundingMode, roundingIncrement); - } + // DecimalFormat does not allow specification of a rounding increment. If anything other + // than 0.0 is used here the resulting DecimalFormat cannot be deserialized because the + // serialization format does not include rounding increment information. + double roundingIncrement = 0.0; + ndf.setRoundingMode(roundingMode, roundingIncrement); } + + public String toString() { return ndf.toString(); } } diff --git a/luni/src/main/java/java/text/DecimalFormatSymbols.java b/luni/src/main/java/java/text/DecimalFormatSymbols.java index 708b291..fba2d6e 100644 --- a/luni/src/main/java/java/text/DecimalFormatSymbols.java +++ b/luni/src/main/java/java/text/DecimalFormatSymbols.java @@ -50,7 +50,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { private char percent; private char perMill; private char monetarySeparator; - private char minusSign; + private String minusSign; private String infinity, NaN, currencySymbol, intlCurrencySymbol; private transient Currency currency; @@ -81,6 +81,11 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * the locale. */ public DecimalFormatSymbols(Locale locale) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + + locale = LocaleData.mapInvalidAndNullLocales(locale); LocaleData localeData = LocaleData.get(locale); this.zeroDigit = localeData.zeroDigit; this.digit = '#'; @@ -179,7 +184,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { groupingSeparator == obj.groupingSeparator && infinity.equals(obj.infinity) && intlCurrencySymbol.equals(obj.intlCurrencySymbol) && - minusSign == obj.minusSign && + minusSign.equals(obj.minusSign) && monetarySeparator == obj.monetarySeparator && NaN.equals(obj.NaN) && patternSeparator == obj.patternSeparator && @@ -288,6 +293,16 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * @return the minus sign as a character. */ public char getMinusSign() { + if (minusSign.length() == 1) { + return minusSign.charAt(0); + } + + throw new UnsupportedOperationException( + "Minus sign spans multiple characters: " + minusSign); + } + + /** @hide */ + public String getMinusSignString() { return minusSign; } @@ -366,7 +381,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { result = 31*result + percent; result = 31*result + perMill; result = 31*result + monetarySeparator; - result = 31*result + minusSign; + result = 31*result + minusSign.hashCode(); result = 31*result + exponentSeparator.hashCode(); result = 31*result + infinity.hashCode(); result = 31*result + NaN.hashCode(); @@ -487,7 +502,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable { * the minus sign character. */ public void setMinusSign(char value) { - this.minusSign = value; + this.minusSign = String.valueOf(value); } /** diff --git a/luni/src/main/java/java/text/FieldPosition.java b/luni/src/main/java/java/text/FieldPosition.java index d5bccc7..c26ba33 100644 --- a/luni/src/main/java/java/text/FieldPosition.java +++ b/luni/src/main/java/java/text/FieldPosition.java @@ -21,152 +21,114 @@ package java.text; * Identifies fields in formatted strings. If a {@code FieldPosition} is passed * to the format method with such a parameter, then the indices will be set to * the start and end indices of the field in the formatted string. - * <p> - * A {@code FieldPosition} can be created by using the integer constants in the + * + * <p>A {@code FieldPosition} can be created by using the integer constants in the * various format classes (for example {@code NumberFormat.INTEGER_FIELD}) or * one of the fields of type {@code Format.Field}. - * <p> - * If more than one field information is needed, the method + * + * <p>If more than one field position is needed, the method * {@link NumberFormat#formatToCharacterIterator(Object)} should be used. */ public class FieldPosition { - private int myField, beginIndex, endIndex; - - private Format.Field myAttribute; - - /** - * Constructs a new {@code FieldPosition} for the specified field. - * - * @param field - * the field to identify. - */ - public FieldPosition(int field) { - myField = field; - } - - /** - * Constructs a new {@code FieldPosition} for the specified {@code Field} - * attribute. - * - * @param attribute - * the field attribute to identify. - */ - public FieldPosition(Format.Field attribute) { - myAttribute = attribute; - myField = -1; - } - - /** - * Constructs a new {@code FieldPosition} for the specified {@code Field} - * attribute and field id. - * - * @param attribute - * the field attribute to identify. - * @param field - * the field to identify. - */ - public FieldPosition(Format.Field attribute, int field) { - myAttribute = attribute; - myField = field; - } - - void clear() { - beginIndex = endIndex = 0; - } - - /** - * Compares the specified object to this field position and indicates if - * they are equal. In order to be equal, {@code object} must be an instance - * of {@code FieldPosition} with the same field, begin index and end index. - * - * @param object - * the object to compare with this object. - * @return {@code true} if the specified object is equal to this field - * position; {@code false} otherwise. - * @see #hashCode - */ - @Override - public boolean equals(Object object) { - if (!(object instanceof FieldPosition)) { - return false; - } - FieldPosition pos = (FieldPosition) object; - return myField == pos.myField && myAttribute == pos.myAttribute - && beginIndex == pos.beginIndex && endIndex == pos.endIndex; - } - - /** - * Returns the index of the beginning of the field. - * - * @return the first index of the field. - */ - public int getBeginIndex() { - return beginIndex; - } - - /** - * Returns the index one past the end of the field. - * - * @return one past the index of the last character in the field. - */ - public int getEndIndex() { - return endIndex; - } - - /** - * Returns the field which is being identified. - * - * @return the field constant. - */ - public int getField() { - return myField; - } - - /** - * Returns the attribute which is being identified. - * - * @return the field. - */ - public Format.Field getFieldAttribute() { - return myAttribute; - } - - @Override - public int hashCode() { - int attributeHash = (myAttribute == null) ? 0 : myAttribute.hashCode(); - return attributeHash + myField * 10 + beginIndex * 100 + endIndex; - } - - /** - * Sets the index of the beginning of the field. - * - * @param index - * the index of the first character in the field. - */ - public void setBeginIndex(int index) { - beginIndex = index; - } - - /** - * Sets the index of the end of the field. - * - * @param index - * one past the index of the last character in the field. - */ - public void setEndIndex(int index) { - endIndex = index; - } - - /** - * Returns the string representation of this field position. - * - * @return the string representation of this field position. - */ - @Override - public String toString() { - return getClass().getName() + "[attribute=" + myAttribute + ", field=" - + myField + ", beginIndex=" + beginIndex + ", endIndex=" - + endIndex + "]"; + private int field; + private int beginIndex; + private int endIndex; + private Format.Field attribute; + + /** + * Constructs a new {@code FieldPosition} for the given field id. + */ + public FieldPosition(int field) { + this.field = field; + } + + /** + * Constructs a new {@code FieldPosition} for the given {@code Field} attribute. + */ + public FieldPosition(Format.Field attribute) { + this.attribute = attribute; + this.field = -1; + } + + /** + * Constructs a new {@code FieldPosition} for the given {@code Field} attribute and field id. + */ + public FieldPosition(Format.Field attribute, int field) { + this.attribute = attribute; + this.field = field; + } + + /** + * Compares the given object to this field position and indicates if + * they are equal. In order to be equal, {@code object} must be an instance + * of {@code FieldPosition} with the same field, begin index and end index. + */ + @Override public boolean equals(Object object) { + if (!(object instanceof FieldPosition)) { + return false; } + FieldPosition pos = (FieldPosition) object; + return field == pos.field && this.attribute == pos.attribute && + beginIndex == pos.beginIndex && endIndex == pos.endIndex; + } + + /** + * Returns the index of the beginning of the field. + */ + public int getBeginIndex() { + return beginIndex; + } + + /** + * Returns the index one past the end of the field. + */ + public int getEndIndex() { + return endIndex; + } + + /** + * Returns the field which is being identified. + */ + public int getField() { + return field; + } + + /** + * Returns the attribute which is being identified. + */ + public Format.Field getFieldAttribute() { + return attribute; + } + + @Override public int hashCode() { + int attributeHash = (attribute == null) ? 0 : attribute.hashCode(); + return attributeHash + field * 10 + beginIndex * 100 + endIndex; + } + + /** + * Sets the index of the beginning of the field. + */ + public void setBeginIndex(int index) { + beginIndex = index; + } + + /** + * Sets the index of the end of the field. + */ + public void setEndIndex(int index) { + endIndex = index; + } + + /** + * Returns the string representation of this field position. + */ + @Override public String toString() { + return getClass().getName() + "[" + + "attribute=" + attribute + + ",field=" + field + + ",beginIndex=" + beginIndex + + ",endIndex=" + endIndex + + "]"; + } } diff --git a/luni/src/main/java/java/text/MessageFormat.java b/luni/src/main/java/java/text/MessageFormat.java index 2ab78db..cf306a7 100644 --- a/luni/src/main/java/java/text/MessageFormat.java +++ b/luni/src/main/java/java/text/MessageFormat.java @@ -45,7 +45,7 @@ import libcore.util.EmptyArray; * behavior. Any locale-specific behavior is defined by the pattern that you * provide as well as the subformats used for inserted arguments. * - * <h4><a name="patterns">Patterns and their interpretation</a></h4> + * <h4><a name="patterns"></a>Patterns and their interpretation</h4> * * {@code MessageFormat} uses patterns of the following form: * <blockquote> @@ -321,7 +321,7 @@ import libcore.util.EmptyArray; * // result now equals {new String("z")} * </pre> * </blockquote> - * <h4><a name="synchronization">Synchronization</a></h4> + * <h4><a name="synchronization"></a>Synchronization</h4> * <p> * Message formats are not synchronized. It is recommended to create separate * format instances for each thread. If multiple threads access a format diff --git a/luni/src/main/java/java/text/NumberFormat.java b/luni/src/main/java/java/text/NumberFormat.java index 36fdd0f..a1f10d4 100644 --- a/luni/src/main/java/java/text/NumberFormat.java +++ b/luni/src/main/java/java/text/NumberFormat.java @@ -155,8 +155,10 @@ public abstract class NumberFormat extends Format { private boolean groupingUsed = true, parseIntegerOnly = false; - private int maximumIntegerDigits = 40, minimumIntegerDigits = 1, - maximumFractionDigits = 3, minimumFractionDigits = 0; + int maximumIntegerDigits = 40; + int minimumIntegerDigits = 1; + int maximumFractionDigits = 3; + int minimumFractionDigits = 0; /** * Used by subclasses. This was public in Java 5. @@ -208,8 +210,7 @@ public abstract class NumberFormat extends Format { * @return the formatted string. */ public final String format(double value) { - return format(value, new StringBuffer(), new FieldPosition(0)) - .toString(); + return format(value, new StringBuffer(), new FieldPosition(0)).toString(); } /** @@ -241,8 +242,7 @@ public abstract class NumberFormat extends Format { * @return the formatted string. */ public final String format(long value) { - return format(value, new StringBuffer(), new FieldPosition(0)) - .toString(); + return format(value, new StringBuffer(), new FieldPosition(0)).toString(); } /** @@ -301,7 +301,11 @@ public abstract class NumberFormat extends Format { double dv = ((Number) object).doubleValue(); return format(dv, buffer, field); } - throw new IllegalArgumentException("Bad class: " + object.getClass()); + if (object == null) { + throw new IllegalArgumentException("Can't format null object"); + } else { + throw new IllegalArgumentException("Bad class: " + object.getClass()); + } } /** @@ -349,6 +353,10 @@ public abstract class NumberFormat extends Format { * @return a {@code NumberFormat} for handling currency values. */ public static NumberFormat getCurrencyInstance(Locale locale) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + return getInstance(LocaleData.get(locale).currencyPattern, locale); } @@ -372,6 +380,10 @@ public abstract class NumberFormat extends Format { * @return a {@code NumberFormat} for handling integers. */ public static NumberFormat getIntegerInstance(Locale locale) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + NumberFormat result = getInstance(LocaleData.get(locale).integerPattern, locale); result.setParseIntegerOnly(true); return result; @@ -465,6 +477,9 @@ public abstract class NumberFormat extends Format { * @return a {@code NumberFormat} for handling {@code Number} objects. */ public static NumberFormat getNumberInstance(Locale locale) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } return getInstance(LocaleData.get(locale).numberPattern, locale); } @@ -492,6 +507,10 @@ public abstract class NumberFormat extends Format { * treated as 5,300%, which is rarely what you intended. */ public static NumberFormat getPercentInstance(Locale locale) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + return getInstance(LocaleData.get(locale).percentPattern, locale); } @@ -514,11 +533,8 @@ public abstract class NumberFormat extends Format { } /** - * Indicates whether this number format only parses integer numbers. Parsing + * Returns true if this number format only parses integer numbers. Parsing * stops if a decimal separator is encountered. - * - * @return {@code true} if this number format only parses integers, - * {@code false} if if parsese integers as well as fractions. */ public boolean isParseIntegerOnly() { return parseIntegerOnly; @@ -742,10 +758,6 @@ public abstract class NumberFormat extends Format { * The instances of this inner class are used as attribute keys and values * in {@code AttributedCharacterIterator} that the * {@link NumberFormat#formatToCharacterIterator(Object)} method returns. - * <p> - * There is no public constructor in this class, the only instances are the - * constants defined here. - * <p> */ public static class Field extends Format.Field { @@ -809,9 +821,6 @@ public abstract class NumberFormat extends Format { /** * Constructs a new instance of {@code NumberFormat.Field} with the * given field name. - * - * @param fieldName - * the field name. */ protected Field(String fieldName) { super(fieldName); diff --git a/luni/src/main/java/java/text/RuleBasedCollator.java b/luni/src/main/java/java/text/RuleBasedCollator.java index cda06db..4e84ef7 100644 --- a/luni/src/main/java/java/text/RuleBasedCollator.java +++ b/luni/src/main/java/java/text/RuleBasedCollator.java @@ -20,243 +20,60 @@ package java.text; import libcore.icu.RuleBasedCollatorICU; /** - * A concrete implementation class for {@code Collation}. - * <p> - * {@code RuleBasedCollator} has the following restrictions for efficiency - * (other subclasses may be used for more complex languages): - * <ol> - * <li> If a French secondary ordering is specified it applies to the whole - * collator object.</li> - * <li> All non-mentioned Unicode characters are at the end of the collation - * order.</li> - * <li> If a character is not located in the {@code RuleBasedCollator}, the - * default Unicode Collation Algorithm (UCA) rule-based table is automatically - * searched as a backup.</li> - * </ol> - * <p> - * The collation table is composed of a list of collation rules, where each rule - * is of three forms: - * <blockquote> - * <pre> - * <modifier> - * <relation> <text-argument> - * <reset> <text-argument> - * </pre> - * </blockquote> - * <p> - * The rule elements are defined as follows: - * <ul type="disc"> - * <li><strong>Modifier</strong>: There is a single modifier which is used to - * specify that all accents (secondary differences) are backwards: - * <ul type=square> - * <li>'@' : Indicates that accents are sorted backwards, as in French. - * </ul> - * </li> - * <li><strong>Relation</strong>: The relations are the following: - * <ul type=square> - * <li>'<' : Greater, as a letter difference (primary) - * <li>';' : Greater, as an accent difference (secondary) - * <li>',' : Greater, as a case difference (tertiary) - * <li>'=' : Equal - * </ul> - * </li> - * <li><strong>Text-Argument</strong>: A text-argument is any sequence of - * characters, excluding special characters (that is, common whitespace - * characters [0009-000D, 0020] and rule syntax characters [0021-002F, - * 003A-0040, 005B-0060, 007B-007E]). If those characters are desired, you can - * put them in single quotes (for example, use '&' for ampersand). Note that - * unquoted white space characters are ignored; for example, {@code b c} is - * treated as {@code bc}.</li> - * <li><strong>Reset</strong>: There is a single reset which is used primarily - * for contractions and expansions, but which can also be used to add a - * modification at the end of a set of rules: - * <ul type=square> - * <li>'&' : Indicates that the next rule follows the position to where the reset - * text-argument would be sorted. - * </ul> - * </li> - * </ul> - * <p> - * This sounds more complicated than it is in practice. For example, the - * following are equivalent ways of expressing the same thing: - * <blockquote> + * A concrete subclass of {@link Collator}. + * It is based on the ICU RuleBasedCollator which implements the + * CLDR and Unicode collation algorithms. + * + * <p>Most of the time, you create a {@link Collator} instance for a {@link java.util.Locale} + * by calling the {@link Collator#getInstance} factory method. + * You can construct a {@code RuleBasedCollator} if you need a custom sort order. + * + * <p>The root collator's sort order is the CLDR root collation order + * which in turn is the Unicode default sort order with a few modifications. + * A {@code RuleBasedCollator} is built from a rule {@code String} which changes the + * sort order of some characters and strings relative to the default order. + * + * <p>A rule string usually contains one or more rule chains. + * A rule chain consists of a reset followed by one or more rules. + * The reset anchors the following rules in the default sort order. + * The rules change the order of the their characters and strings + * relative to the reset point. + * + * <p>A reset is an ampersand {@code &} followed by one or more characters for the reset position. + * A rule is a relation operator, which specifies the level of difference, + * also followed by one or more characters. + * A multi-character rule creates a "contraction". + * A multi-character reset position usually creates "expansions". + * + * <p>For example, the following rules + * make "ä" sort with a diacritic-like (secondary) difference from "ae" + * (like in German phonebook sorting), + * and make "å" and "aa" sort as a base letter (primary) after "z" (like in Danish). + * Uppercase forms sort with a case-like (tertiary) difference after their lowercase forms. * - * <pre> - * a < b < c - * a < b & b < c - * a < c & a < b - * </pre> - * - * </blockquote> - * <p> - * Notice that the order is important, as the subsequent item goes immediately - * after the text-argument. The following are not equivalent: * <blockquote> - * * <pre> - * a < b & a < c - * a < c & a < b + * &AE<<ä <<<Ä + * &z<å<<<Å<<<aa<<<Aa<<<AA * </pre> - * * </blockquote> - * <p> - * Either the text-argument must already be present in the sequence, or some - * initial substring of the text-argument must be present. For example - * {@code "a < b & ae < e"} is valid since "a" is present in the sequence before - * "ae" is reset. In this latter case, "ae" is not entered and treated as a - * single character; instead, "e" is sorted as if it were expanded to two - * characters: "a" followed by an "e". This difference appears in natural - * languages: in traditional Spanish "ch" is treated as if it contracts to a - * single character (expressed as {@code "c < ch < d"}), while in traditional - * German a-umlaut is treated as if it expands to two characters (expressed as - * {@code "a,A < b,B ... & ae;\u00e3 & AE;\u00c3"}, where \u00e3 and \u00c3 - * are the escape sequences for a-umlaut). - * <h4>Ignorable Characters</h4> - * <p> - * For ignorable characters, the first rule must start with a relation (the - * examples we have used above are really fragments; {@code "a < b"} really - * should be {@code "< a < b"}). If, however, the first relation is not - * {@code "<"}, then all text-arguments up to the first {@code "<"} are - * ignorable. For example, {@code ", - < a < b"} makes {@code "-"} an ignorable - * character. - * <h4>Normalization and Accents</h4> - * <p> - * {@code RuleBasedCollator} automatically processes its rule table to include - * both pre-composed and combining-character versions of accented characters. - * Even if the provided rule string contains only base characters and separate - * combining accent characters, the pre-composed accented characters matching - * all canonical combinations of characters from the rule string will be entered - * in the table. - * <p> - * This allows you to use a RuleBasedCollator to compare accented strings even - * when the collator is set to NO_DECOMPOSITION. However, if the strings to be - * collated contain combining sequences that may not be in canonical order, you - * should set the collator to CANONICAL_DECOMPOSITION to enable sorting of - * combining sequences. For more information, see <a - * href="http://www.aw.com/devpress">The Unicode Standard, Version 3.0</a>. - * <h4>Errors</h4> - * <p> - * The following rules are not valid: - * <ul type="disc"> - * <li>A text-argument contains unquoted punctuation symbols, for example - * {@code "a < b-c < d"}.</li> - * <li>A relation or reset character is not followed by a text-argument, for - * example {@code "a < , b"}.</li> - * <li>A reset where the text-argument (or an initial substring of the - * text-argument) is not already in the sequence or allocated in the default UCA - * table, for example {@code "a < b & e < f"}.</li> - * </ul> - * <p> - * If you produce one of these errors, {@code RuleBasedCollator} throws a - * {@code ParseException}. - * <h4>Examples</h4> - * <p> - * Normally, to create a rule-based collator object, you will use - * {@code Collator}'s factory method {@code getInstance}. However, to create a - * rule-based collator object with specialized rules tailored to your needs, you - * construct the {@code RuleBasedCollator} with the rules contained in a - * {@code String} object. For example: - * <blockquote> - * - * <pre> - * String Simple = "< a < b < c < d"; - * RuleBasedCollator mySimple = new RuleBasedCollator(Simple); - * </pre> - * - * </blockquote> - * <p> - * Or: - * <blockquote> - * - * <pre> - * String Norwegian = "< a,A< b,B< c,C< d,D< e,E< f,F< g,G< h,H< i,I" - * + "< j,J< k,K< l,L< m,M< n,N< o,O< p,P< q,Q< r,R" - * + "< s,S< t,T< u,U< v,V< w,W< x,X< y,Y< z,Z" - * + "< \u00E5=a\u030A,\u00C5=A\u030A" - * + ";aa,AA< \u00E6,\u00C6< \u00F8,\u00D8"; - * RuleBasedCollator myNorwegian = new RuleBasedCollator(Norwegian); - * </pre> - * - * </blockquote> - * <p> - * Combining {@code Collator}s is as simple as concatenating strings. Here is - * an example that combines two {@code Collator}s from two different locales: - * <blockquote> - * - * <pre> - * // Create an en_US Collator object - * RuleBasedCollator en_USCollator = (RuleBasedCollator)Collator - * .getInstance(new Locale("en", "US", "")); - * - * // Create a da_DK Collator object - * RuleBasedCollator da_DKCollator = (RuleBasedCollator)Collator - * .getInstance(new Locale("da", "DK", "")); * - * // Combine the two collators - * // First, get the collation rules from en_USCollator - * String en_USRules = en_USCollator.getRules(); - * - * // Second, get the collation rules from da_DKCollator - * String da_DKRules = da_DKCollator.getRules(); - * - * RuleBasedCollator newCollator = new RuleBasedCollator(en_USRules + da_DKRules); - * // newCollator has the combined rules - * </pre> - * - * </blockquote> - * <p> - * The next example shows to make changes on an existing table to create a new - * {@code Collator} object. For example, add {@code "& C < ch, cH, Ch, CH"} to - * the {@code en_USCollator} object to create your own: - * <blockquote> - * - * <pre> - * // Create a new Collator object with additional rules - * String addRules = "& C < ch, cH, Ch, CH"; - * - * RuleBasedCollator myCollator = new RuleBasedCollator(en_USCollator + addRules); - * // myCollator contains the new rules - * </pre> - * - * </blockquote> - * <p> - * The following example demonstrates how to change the order of non-spacing - * accents: - * <blockquote> - * - * <pre> - * // old rule - * String oldRules = "= \u00a8 ; \u00af ; \u00bf" + "< a , A ; ae, AE ; \u00e6 , \u00c6" - * + "< b , B < c, C < e, E & C < d, D"; - * - * // change the order of accent characters - * String addOn = "& \u00bf ; \u00af ; \u00a8;"; - * - * RuleBasedCollator myCollator = new RuleBasedCollator(oldRules + addOn); - * </pre> - * - * </blockquote> - * <p> - * The last example shows how to put new primary ordering in before the default - * setting. For example, in the Japanese {@code Collator}, you can either sort - * English characters before or after Japanese characters: - * <blockquote> - * - * <pre> - * // get en_US Collator rules - * RuleBasedCollator en_USCollator = (RuleBasedCollator) - * Collator.getInstance(Locale.US); - * - * // add a few Japanese character to sort before English characters - * // suppose the last character before the first base letter 'a' in - * // the English collation rule is \u30A2 - * String jaString = "& \u30A2 , \u30FC < \u30C8"; + * <p>For details see + * <ul> + * <li>CLDR <a href="http://www.unicode.org/reports/tr35/tr35-collation.html#Rules">Collation Rule Syntax</a> + * <li>ICU User Guide <a href="http://userguide.icu-project.org/collation/customization">Collation Customization</a> + * </ul> * - * RuleBasedCollator myJapaneseCollator = - * new RuleBasedCollator(en_USCollator.getRules() + jaString); - * </pre> + * <p>Note: earlier versions of {@code RuleBasedCollator} up to and including Android 4.4 (KitKat) + * allowed the omission of the reset from the first rule chain. + * This was interpreted as an implied reset after the last non-Han script in the default order. + * However, this is not a useful reset position, except for large tailorings of + * Han characters themselves. + * Starting with the CLDR 24 collation specification and the ICU 53 implementation, + * the initial reset is required. * - * </blockquote> + * <p>If the rule string does not follow the syntax, then {@code RuleBasedCollator} throws a + * {@code ParseException}. */ public class RuleBasedCollator extends Collator { RuleBasedCollator(RuleBasedCollatorICU wrapper) { @@ -265,13 +82,11 @@ public class RuleBasedCollator extends Collator { /** * Constructs a new instance of {@code RuleBasedCollator} using the - * specified {@code rules}. The {@code rules} are usually either - * hand-written based on the {@link RuleBasedCollator class description} or - * the result of a former {@link #getRules()} call. + * specified {@code rules}. (See the {@link RuleBasedCollator class description}.) * <p> - * Note that the {@code rules} are actually interpreted as a delta to the - * standard Unicode Collation Algorithm (UCA). This differs - * slightly from other implementations which work with full {@code rules} + * Note that the {@code rules} are interpreted as a delta to the + * default sort order. This differs + * from other implementations which work with full {@code rules} * specifications and may result in different behavior. * * @param rules @@ -286,9 +101,6 @@ public class RuleBasedCollator extends Collator { if (rules == null) { throw new NullPointerException("rules == null"); } - if (rules.isEmpty()) { - throw new ParseException("empty rules", 0); - } try { icuColl = new RuleBasedCollatorICU(rules); } catch (Exception e) { @@ -336,14 +148,9 @@ public class RuleBasedCollator extends Collator { /** * Returns the collation rules of this collator. These {@code rules} can be * fed into the {@code RuleBasedCollator(String)} constructor. - * <p> - * Note that the {@code rules} are actually interpreted as a delta to the - * standard Unicode Collation Algorithm (UCA). Hence, an empty {@code rules} - * string results in the default UCA rules being applied. This differs - * slightly from other implementations which work with full {@code rules} - * specifications and may result in different behavior. * - * @return the collation rules. + * <p>The returned string will be empty unless you constructed the instance yourself. + * The string forms of the collation rules are omitted to save space on the device. */ public String getRules() { return icuColl.getRules(); @@ -367,13 +174,6 @@ public class RuleBasedCollator extends Collator { * the collation rules, strength and decomposition mode for this * {@code RuleBasedCollator}. See the {@code Collator} class description * for an example of use. - * <p> - * General recommendation: If comparisons are to be done with the same strings - * multiple times, it is more efficient to generate {@code CollationKey} - * objects for the strings and use - * {@code CollationKey.compareTo(CollationKey)} for the comparisons. If each - * string is compared to only once, using - * {@code RuleBasedCollator.compare(String, String)} has better performance. * * @param source * the source text. diff --git a/luni/src/main/java/java/text/SimpleDateFormat.java b/luni/src/main/java/java/text/SimpleDateFormat.java index 5fd8a56..8f83ff7 100644 --- a/luni/src/main/java/java/text/SimpleDateFormat.java +++ b/luni/src/main/java/java/text/SimpleDateFormat.java @@ -517,7 +517,8 @@ public class SimpleDateFormat extends DateFormat { int next, last = -1, count = 0; calendar.setTime(date); if (field != null) { - field.clear(); + field.setBeginIndex(0); + field.setEndIndex(0); } final int patternLength = pattern.length(); @@ -623,8 +624,7 @@ public class SimpleDateFormat extends DateFormat { break; case MILLISECOND_FIELD: dateFormatField = Field.MILLISECOND; - int value = calendar.get(Calendar.MILLISECOND); - appendNumber(buffer, count, value); + appendMilliseconds(buffer, count, calendar.get(Calendar.MILLISECOND)); break; case STAND_ALONE_DAY_OF_WEEK_FIELD: dateFormatField = Field.DAY_OF_WEEK; @@ -740,15 +740,9 @@ public class SimpleDateFormat extends DateFormat { TimeZone tz = calendar.getTimeZone(); boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0); int style = count < 4 ? TimeZone.SHORT : TimeZone.LONG; - if (!formatData.customZoneStrings) { - buffer.append(tz.getDisplayName(daylight, style, formatData.locale)); - return; - } - // We can't call TimeZone.getDisplayName() because it would not use - // the custom DateFormatSymbols of this SimpleDateFormat. - String custom = TimeZoneNames.getDisplayName(formatData.zoneStrings, tz.getID(), daylight, style); - if (custom != null) { - buffer.append(custom); + String zoneString = formatData.getTimeZoneDisplayName(tz, daylight, style); + if (zoneString != null) { + buffer.append(zoneString); return; } } @@ -759,21 +753,29 @@ public class SimpleDateFormat extends DateFormat { // See http://www.unicode.org/reports/tr35/#Date_Format_Patterns for the different counts. // @param generalTimeZone "GMT-08:00" rather than "-0800". private void appendNumericTimeZone(StringBuffer buffer, int count, boolean generalTimeZone) { - int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); - char sign = '+'; - if (offset < 0) { - sign = '-'; - offset = -offset; - } - if (generalTimeZone || count == 4) { - buffer.append("GMT"); + int offsetMillis = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); + boolean includeGmt = generalTimeZone || count == 4; + boolean includeMinuteSeparator = generalTimeZone || count >= 4; + buffer.append(TimeZone.createGmtOffsetString(includeGmt, includeMinuteSeparator, + offsetMillis)); + } + + private void appendMilliseconds(StringBuffer buffer, int count, int value) { + // Unlike other fields, milliseconds are truncated by count. So 361 formatted SS is "36". + numberFormat.setMinimumIntegerDigits((count > 3) ? 3 : count); + numberFormat.setMaximumIntegerDigits(10); + // We need to left-justify. + if (count == 1) { + value /= 100; + } else if (count == 2) { + value /= 10; } - buffer.append(sign); - appendNumber(buffer, 2, offset / 3600000); - if (generalTimeZone || count >= 4) { - buffer.append(':'); + FieldPosition p = new FieldPosition(0); + numberFormat.format(Integer.valueOf(value), buffer, p); + if (count > 3) { + numberFormat.setMinimumIntegerDigits(count - 3); + numberFormat.format(Integer.valueOf(0), buffer, p); } - appendNumber(buffer, 2, (offset % 3600000) / 60000); } private void appendNumber(StringBuffer buffer, int count, int value) { @@ -905,8 +907,7 @@ public class SimpleDateFormat extends DateFormat { field = Calendar.SECOND; break; case MILLISECOND_FIELD: - field = Calendar.MILLISECOND; - break; + return parseFractionalSeconds(string, offset, absolute); case STAND_ALONE_DAY_OF_WEEK_FIELD: return parseDayOfWeek(string, offset, true); case DAY_OF_WEEK_FIELD: @@ -951,6 +952,31 @@ public class SimpleDateFormat extends DateFormat { return offset; } + /** + * Parses the fractional seconds section of a formatted date and assigns + * it to the {@code Calendar.MILLISECOND} field. Note that fractional seconds + * are somewhat unique, because they are zero suffixed. + */ + private int parseFractionalSeconds(String string, int offset, int count) { + final ParsePosition parsePosition = new ParsePosition(offset); + final Number fractionalSeconds = parseNumber(count, string, parsePosition); + if (fractionalSeconds == null) { + return -parsePosition.getErrorIndex() - 1; + } + + // NOTE: We could've done this using two parses instead. The first parse + // looking at |count| digits (to verify the date matched the format), and + // then a second parse that consumed just the first three digits. That + // would've avoided the floating point arithmetic, but would've demanded + // that we round values ourselves. + final double result = fractionalSeconds.doubleValue(); + final int numDigitsParsed = parsePosition.getIndex() - offset; + final double divisor = Math.pow(10, numDigitsParsed); + + calendar.set(Calendar.MILLISECOND, (int) ((result / divisor) * 1000)); + return parsePosition.getIndex(); + } + private int parseDayOfWeek(String string, int offset, boolean standAlone) { LocaleData ld = formatData.localeData; int index = parseText(string, offset, @@ -1085,25 +1111,7 @@ public class SimpleDateFormat extends DateFormat { } if (max == 0) { position.setIndex(index); - Number n = numberFormat.parse(string, position); - // In RTL locales, NumberFormat might have parsed "2012-" in an ISO date as the - // negative number -2012. - // Ideally, we wouldn't have this broken API that exposes a NumberFormat and expects - // us to use it. The next best thing would be a way to ask the NumberFormat to parse - // positive numbers only, but icu4c supports negative (BCE) years. The best we can do - // is try to recognize when icu4c has done this, and undo it. - if (n != null && n.longValue() < 0) { - if (numberFormat instanceof DecimalFormat) { - DecimalFormat df = (DecimalFormat) numberFormat; - char lastChar = string.charAt(position.getIndex() - 1); - char minusSign = df.getDecimalFormatSymbols().getMinusSign(); - if (lastChar == minusSign) { - n = Long.valueOf(-n.longValue()); // Make the value positive. - position.setIndex(position.getIndex() - 1); // Spit out the negative sign. - } - } - } - return n; + return numberFormat.parse(string, position); } int result = 0; @@ -1168,6 +1176,8 @@ public class SimpleDateFormat extends DateFormat { if (foundGMT) { offset += 3; } + + // Check for an offset, which may have been preceded by "GMT" char sign; if (offset < string.length() && ((sign = string.charAt(offset)) == '+' || sign == '-')) { ParsePosition position = new ParsePosition(offset + 1); @@ -1195,10 +1205,14 @@ public class SimpleDateFormat extends DateFormat { calendar.setTimeZone(new SimpleTimeZone(raw, "")); return position.getIndex(); } + + // If there was "GMT" but no offset. if (foundGMT) { calendar.setTimeZone(TimeZone.getTimeZone("GMT")); return offset; } + + // Exhaustively look for the string in this DateFormat's localized time zone strings. for (String[] row : formatData.internalZoneStrings()) { for (int i = TimeZoneNames.LONG_NAME; i < TimeZoneNames.NAME_COUNT; ++i) { if (row[i] == null) { diff --git a/luni/src/main/java/java/text/spi/BreakIteratorProvider.java b/luni/src/main/java/java/text/spi/BreakIteratorProvider.java deleted file mode 100644 index a28bc53..0000000 --- a/luni/src/main/java/java/text/spi/BreakIteratorProvider.java +++ /dev/null @@ -1,90 +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 java.text.spi; - -import java.text.BreakIterator; -import java.util.Locale; -import java.util.spi.LocaleServiceProvider; - -/** - * This abstract class should be extended by service providers that provide - * instances of {@code BreakIterator}. - * <p>Note that Android does not support user-supplied locale service providers. - * @since 1.6 - * @hide - */ -public abstract class BreakIteratorProvider extends LocaleServiceProvider { - /** - * Default constructor, for use by subclasses. - */ - protected BreakIteratorProvider() { - // Do nothing. - } - - /** - * Returns an instance of {@code BreakIterator} for word breaks in the - * given locale. - * - * @param locale the locale - * @return an instance of {@code BreakIterator} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract BreakIterator getWordInstance(Locale locale); - - /** - * Returns an instance of {@code BreakIterator} for line breaks in the - * given locale. - * - * @param locale the locale - * @return an instance of {@code BreakIterator} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract BreakIterator getLineInstance(Locale locale); - - /** - * Returns an instance of {@code BreakIterator} for character breaks in the - * given locale. - * - * @param locale the locale - * @return an instance of {@code BreakIterator} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract BreakIterator getCharacterInstance(Locale locale); - - /** - * Returns an instance of {@code BreakIterator} for sentence breaks in the - * given locale. - * - * @param locale the locale - * @return an instance of {@code BreakIterator} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract BreakIterator getSentenceInstance(Locale locale); -} diff --git a/luni/src/main/java/java/text/spi/CollatorProvider.java b/luni/src/main/java/java/text/spi/CollatorProvider.java deleted file mode 100644 index 0862432..0000000 --- a/luni/src/main/java/java/text/spi/CollatorProvider.java +++ /dev/null @@ -1,50 +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 java.text.spi; - -import java.text.Collator; -import java.util.Locale; -import java.util.spi.LocaleServiceProvider; - -/** - * This abstract class should be extended by service providers which provide - * instances of {@code Collator}. - * <p>Note that Android does not support user-supplied locale service providers. - * @since 1.6 - * @hide - */ -public abstract class CollatorProvider extends LocaleServiceProvider { - /** - * Default constructor, for use by subclasses. - */ - protected CollatorProvider() { - // Do nothing. - } - - /** - * Returns an instance of {@code Collator} for the given locale. - * - * @param locale the locale - * @return an instance of {@code Collator} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract Collator getInstance(Locale locale); -} diff --git a/luni/src/main/java/java/text/spi/DateFormatProvider.java b/luni/src/main/java/java/text/spi/DateFormatProvider.java deleted file mode 100644 index 59fefed..0000000 --- a/luni/src/main/java/java/text/spi/DateFormatProvider.java +++ /dev/null @@ -1,81 +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 java.text.spi; - -import java.text.DateFormat; -import java.util.Locale; -import java.util.spi.LocaleServiceProvider; - -/** - * This abstract class should be extended by service providers that provide - * instances of {@code DateFormat}. - * <p>Note that Android does not support user-supplied locale service providers. - * @since 1.6 - * @hide - */ -public abstract class DateFormatProvider extends LocaleServiceProvider { - /** - * Default constructor, for use by subclasses. - */ - protected DateFormatProvider() { - // Do nothing. - } - - /** - * Returns an instance of {@code DateFormat} that formats times - * in the given style for the given locale. - * - * @param style the given time formatting style. - * @param locale the locale - * @return an instance of {@code DateFormat} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract DateFormat getTimeInstance(int style, Locale locale); - - /** - * Returns an instance of {@code DateFormat} that formats dates - * in the given style for the given locale. - * - * @param style the given date formatting style. - * @param locale the locale - * @return an instance of {@code DateFormat} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract DateFormat getDateInstance(int style, Locale locale); - - /** - * Returns an instance of {@code DateFormat} that formats dates and times - * in the given style for the given locale. - * - * @param dateStyle the given date formatting style. - * @param timeStyle the given time formatting style. - * @param locale the locale - * @return an instance of {@code DateFormat} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale); -} diff --git a/luni/src/main/java/java/text/spi/DateFormatSymbolsProvider.java b/luni/src/main/java/java/text/spi/DateFormatSymbolsProvider.java deleted file mode 100644 index cb34b71..0000000 --- a/luni/src/main/java/java/text/spi/DateFormatSymbolsProvider.java +++ /dev/null @@ -1,50 +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 java.text.spi; - -import java.text.DateFormatSymbols; -import java.util.Locale; -import java.util.spi.LocaleServiceProvider; - -/** - * This abstract class should be extended by service providers that provide - * instances of {@code DateFormatSymbols}. - * <p>Note that Android does not support user-supplied locale service providers. - * @since 1.6 - * @hide - */ -public abstract class DateFormatSymbolsProvider extends LocaleServiceProvider { - /** - * Default constructor, for use by subclasses. - */ - protected DateFormatSymbolsProvider() { - // Do nothing. - } - - /** - * Returns an instance of {@code DateFormatSymbols} for the given locale. - * - * @param locale the locale - * @return an instance of {@code DateFormatSymbols} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract DateFormatSymbols getInstance(Locale locale); -} diff --git a/luni/src/main/java/java/text/spi/DecimalFormatSymbolsProvider.java b/luni/src/main/java/java/text/spi/DecimalFormatSymbolsProvider.java deleted file mode 100644 index f9f5e7d..0000000 --- a/luni/src/main/java/java/text/spi/DecimalFormatSymbolsProvider.java +++ /dev/null @@ -1,51 +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 java.text.spi; - -import java.text.DecimalFormatSymbols; -import java.util.Locale; -import java.util.spi.LocaleServiceProvider; - -/** - * This abstract class should be extended by service providers that provide - * instances of {@code DecimalFormatSymbols}. - * <p>Note that Android does not support user-supplied locale service providers. - * @since 1.6 - * @hide - */ -public abstract class DecimalFormatSymbolsProvider extends LocaleServiceProvider { - /** - * Default constructor, for use by subclasses. - */ - protected DecimalFormatSymbolsProvider() { - // Do nothing. - } - - /** - * Returns an instance of {@code DecimalFormatSymbols} for the given locale. - * - * @param locale the locale - * @return an instance of {@code DecimalFormatSymbols} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract DecimalFormatSymbols getInstance(Locale locale); - -} diff --git a/luni/src/main/java/java/text/spi/NumberFormatProvider.java b/luni/src/main/java/java/text/spi/NumberFormatProvider.java deleted file mode 100644 index c889f78..0000000 --- a/luni/src/main/java/java/text/spi/NumberFormatProvider.java +++ /dev/null @@ -1,93 +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 java.text.spi; - -import java.text.NumberFormat; -import java.util.Locale; -import java.util.spi.LocaleServiceProvider; - -/** - * This abstract class should be extended by service providers that provide - * {@code NumberFormat} instances. - * <p>Note that Android does not support user-supplied locale service providers. - * @since 1.6 - * @hide - */ -public abstract class NumberFormatProvider extends LocaleServiceProvider { - /** - * Default constructor, for use by subclasses. - */ - protected NumberFormatProvider() { - // Do nothing. - } - - /** - * Returns an instance of {@code NumberFormat} that formats - * monetary values for the given locale. - * - * @param locale the locale - * @return an instance of {@code NumberFormat} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract NumberFormat getCurrencyInstance(Locale locale); - - /** - * Returns an instance of {@code NumberFormat} that formats - * integer values for the given locale. The returned {@code NumberFormat} - * is configured to round floating point numbers to the nearest integer - * using half-even rounding mode for formatting, and to parse only the - * integer part of an input string. - * - * @param locale the locale - * @return an instance of {@code NumberFormat} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract NumberFormat getIntegerInstance(Locale locale); - - /** - * Returns an instance of {@code NumberFormat} class for general - * use in the given locale. - * - * @param locale the locale - * @return an instance of {@code NumberFormat} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract NumberFormat getNumberInstance(Locale locale); - - /** - * Returns an instance of {@code NumberFormat} class that formats - * percentage values for the given locale. - * - * @param locale the locale - * @return an instance of {@code NumberFormat} - * @throws NullPointerException if {@code locale == null} - * @throws IllegalArgumentException - * if locale isn't one of the locales returned from - * getAvailableLocales(). - */ - public abstract NumberFormat getPercentInstance(Locale locale); -} diff --git a/luni/src/main/java/java/util/Arrays.java b/luni/src/main/java/java/util/Arrays.java index d3e05a0..3b477d0 100644 --- a/luni/src/main/java/java/util/Arrays.java +++ b/luni/src/main/java/java/util/Arrays.java @@ -1319,31 +1319,25 @@ public class Arrays { /* * element is an array */ - if (!cl.isPrimitive()) { + if (element instanceof Object[]) { return deepHashCode((Object[]) element); - } - if (cl.equals(int.class)) { + } else if (cl == int.class) { return hashCode((int[]) element); - } - if (cl.equals(char.class)) { + } else if (cl == char.class) { return hashCode((char[]) element); - } - if (cl.equals(boolean.class)) { + } else if (cl == boolean.class) { return hashCode((boolean[]) element); - } - if (cl.equals(byte.class)) { + } else if (cl == byte.class) { return hashCode((byte[]) element); - } - if (cl.equals(long.class)) { + } else if (cl == long.class) { return hashCode((long[]) element); - } - if (cl.equals(float.class)) { + } else if (cl == float.class) { return hashCode((float[]) element); - } - if (cl.equals(double.class)) { + } else if (cl == double.class) { return hashCode((double[]) element); + } else { + return hashCode((short[]) element); } - return hashCode((short[]) element); } /** @@ -1665,32 +1659,25 @@ public class Arrays { /* * compare as arrays */ - if (!cl1.isPrimitive()) { + if (e1 instanceof Object[]) { return deepEquals((Object[]) e1, (Object[]) e2); - } - - if (cl1.equals(int.class)) { + } else if (cl1 == int.class) { return equals((int[]) e1, (int[]) e2); - } - if (cl1.equals(char.class)) { + } else if (cl1 == char.class) { return equals((char[]) e1, (char[]) e2); - } - if (cl1.equals(boolean.class)) { + } else if (cl1 == boolean.class) { return equals((boolean[]) e1, (boolean[]) e2); - } - if (cl1.equals(byte.class)) { + } else if (cl1 == byte.class) { return equals((byte[]) e1, (byte[]) e2); - } - if (cl1.equals(long.class)) { + } else if (cl1 == long.class) { return equals((long[]) e1, (long[]) e2); - } - if (cl1.equals(float.class)) { + } else if (cl1 == float.class) { return equals((float[]) e1, (float[]) e2); - } - if (cl1.equals(double.class)) { + } else if (cl1 == double.class) { return equals((double[]) e1, (double[]) e2); + } else { + return equals((short[]) e1, (short[]) e2); } - return equals((short[]) e1, (short[]) e2); } /** diff --git a/luni/src/main/java/java/util/Calendar.java b/luni/src/main/java/java/util/Calendar.java index 4ed2ad1..fc4cef6 100644 --- a/luni/src/main/java/java/util/Calendar.java +++ b/luni/src/main/java/java/util/Calendar.java @@ -642,13 +642,24 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca /** * Field number for {@code get} and {@code set} indicating the - * raw offset from GMT in milliseconds. + * raw (non-DST) offset from GMT in milliseconds. + * Equivalent to {@link java.util.TimeZone#getRawOffset}. + * + * <p>To determine the total offset from GMT at the time represented + * by this calendar, you will need to add the {@code ZONE_OFFSET} and + * {@code DST_OFFSET} fields. */ public static final int ZONE_OFFSET = 15; /** * Field number for {@code get} and {@code set} indicating the - * daylight savings offset in milliseconds. + * daylight savings offset from the {@code ZONE_OFFSET} in milliseconds. + * Equivalent to {@link java.util.TimeZone#getDSTSavings} if the represented time + * falls inside DST, or 0 otherwise. + * + * <p>To determine the total offset from GMT at the time represented + * by this calendar, you will need to add the {@code ZONE_OFFSET} and + * {@code DST_OFFSET} fields. */ public static final int DST_OFFSET = 16; @@ -716,6 +727,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca */ protected Calendar(TimeZone timezone, Locale locale) { this(timezone); + locale = LocaleData.mapInvalidAndNullLocales(locale); LocaleData localeData = LocaleData.get(locale); setFirstDayOfWeek(localeData.firstDayOfWeek.intValue()); setMinimalDaysInFirstWeek(localeData.minimalDaysInFirstWeek.intValue()); diff --git a/luni/src/main/java/java/util/Collections.java b/luni/src/main/java/java/util/Collections.java index ce4e579..4541d64 100644 --- a/luni/src/main/java/java/util/Collections.java +++ b/luni/src/main/java/java/util/Collections.java @@ -1212,7 +1212,7 @@ public class Collections { int length = c.size(); Object[] result = new Object[length]; Iterator<?> it = iterator(); - for (int i = length; --i >= 0;) { + for (int i = 0; i < length; i++) { result[i] = it.next(); } return result; diff --git a/luni/src/main/java/java/util/Currency.java b/luni/src/main/java/java/util/Currency.java index b5b04a0..f43ddae 100644 --- a/luni/src/main/java/java/util/Currency.java +++ b/luni/src/main/java/java/util/Currency.java @@ -35,7 +35,7 @@ public final class Currency implements Serializable { private Currency(String currencyCode) { this.currencyCode = currencyCode; - String symbol = ICU.getCurrencySymbol(Locale.US.toString(), currencyCode); + String symbol = ICU.getCurrencySymbol(Locale.US, currencyCode); if (symbol == null) { throw new IllegalArgumentException("Unsupported ISO 4217 currency code: " + currencyCode); @@ -65,6 +65,9 @@ public final class Currency implements Serializable { */ public static Currency getInstance(Locale locale) { synchronized (localesToCurrencies) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } Currency currency = localesToCurrencies.get(locale); if (currency != null) { return currency; @@ -123,7 +126,7 @@ public final class Currency implements Serializable { * @since 1.7 */ public String getDisplayName(Locale locale) { - return ICU.getCurrencyDisplayName(locale.toString(), currencyCode); + return ICU.getCurrencyDisplayName(locale, currencyCode); } /** @@ -146,10 +149,9 @@ public final class Currency implements Serializable { * <p>If there is no locale-specific currency symbol, the ISO 4217 currency code is returned. */ public String getSymbol(Locale locale) { - if (locale.getCountry().length() == 0) { - return currencyCode; + if (locale == null) { + throw new NullPointerException("locale == null"); } - // Check the locale first, in case the locale has the same currency. LocaleData localeData = LocaleData.get(locale); if (localeData.internationalCurrencySymbol.equals(currencyCode)) { @@ -157,7 +159,7 @@ public final class Currency implements Serializable { } // Try ICU, and fall back to the currency code if ICU has nothing. - String symbol = ICU.getCurrencySymbol(locale.toString(), currencyCode); + String symbol = ICU.getCurrencySymbol(locale, currencyCode); return symbol != null ? symbol : currencyCode; } diff --git a/luni/src/main/java/java/util/EnumMap.java b/luni/src/main/java/java/util/EnumMap.java index 827fb51..dfacb46 100644 --- a/luni/src/main/java/java/util/EnumMap.java +++ b/luni/src/main/java/java/util/EnumMap.java @@ -174,7 +174,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements @Override @SuppressWarnings("unchecked") public String toString() { - if (-1 == prePosition) { + if (prePosition == -1) { return super.toString(); } return type.get( @@ -183,7 +183,7 @@ public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements } private void checkStatus() { - if (-1 == prePosition) { + if (prePosition == -1) { throw new IllegalStateException(); } } diff --git a/luni/src/main/java/java/util/Formatter.java b/luni/src/main/java/java/util/Formatter.java index 98bdccc..dd9a09a 100644 --- a/luni/src/main/java/java/util/Formatter.java +++ b/luni/src/main/java/java/util/Formatter.java @@ -2065,7 +2065,7 @@ public final class Formatter implements Closeable, Flushable { formatToken.setPrecision(FormatToken.UNSET); int startIndex = 0; - if (result.charAt(0) == localeData.minusSign) { + if (startsWithMinusSign(result, localeData.minusSign)) { if (formatToken.flagParenthesis) { return wrapParentheses(result); } @@ -2081,8 +2081,9 @@ public final class Formatter implements Closeable, Flushable { } char firstChar = result.charAt(0); - if (formatToken.flagZero && (firstChar == '+' || firstChar == localeData.minusSign)) { - startIndex = 1; + if (formatToken.flagZero && (firstChar == '+' || + startsWithMinusSign(result, localeData.minusSign))) { + startIndex = localeData.minusSign.length(); } if (conversionType == 'a' || conversionType == 'A') { @@ -2091,6 +2092,20 @@ public final class Formatter implements Closeable, Flushable { return padding(result, startIndex); } + private static boolean startsWithMinusSign(CharSequence cs, String minusSign) { + if (cs.length() < minusSign.length()) { + return false; + } + + for (int i = 0; i < minusSign.length(); ++i) { + if (minusSign.charAt(i) != cs.charAt(i)) { + return false; + } + } + + return true; + } + private void transformE(StringBuilder result) { // All zeros in this method are *pattern* characters, so no localization. final int precision = formatToken.getPrecision(); diff --git a/luni/src/main/java/java/util/GregorianCalendar.java b/luni/src/main/java/java/util/GregorianCalendar.java index 9ff9ccc..be96684 100644 --- a/luni/src/main/java/java/util/GregorianCalendar.java +++ b/luni/src/main/java/java/util/GregorianCalendar.java @@ -331,7 +331,16 @@ public class GregorianCalendar extends Calendar { setTimeInMillis(System.currentTimeMillis()); } - GregorianCalendar(boolean ignored) { + /** + * A minimum-cost constructor that does not initialize the current time or perform any date + * calculations. For use internally when the time will be set later. Other constructors, such as + * {@link GregorianCalendar#GregorianCalendar()}, set the time to the current system clock + * and recalculate the fields incurring unnecessary cost when the time or fields will be set + * later. + * + * @hide used internally + */ + public GregorianCalendar(boolean ignored) { super(TimeZone.getDefault()); setFirstDayOfWeek(SUNDAY); setMinimalDaysInFirstWeek(1); @@ -458,16 +467,17 @@ public class GregorianCalendar extends Calendar { complete(); } - private void fullFieldsCalc(long timeVal, int zoneOffset) { + private void fullFieldsCalc() { int millis = (int) (time % 86400000); - long days = timeVal / 86400000; + long days = time / 86400000; if (millis < 0) { millis += 86400000; days--; } - // Cannot add ZONE_OFFSET to time as it might overflow - millis += zoneOffset; + // Adding fields[ZONE_OFFSET] to time might make it overflow, so we add + // it to millis (the number of milliseconds in the current day) instead. + millis += fields[ZONE_OFFSET]; while (millis < 0) { millis += 86400000; days--; @@ -477,9 +487,9 @@ public class GregorianCalendar extends Calendar { days++; } - int dayOfYear = computeYearAndDay(days, timeVal + zoneOffset); + int dayOfYear = computeYearAndDay(days, time + fields[ZONE_OFFSET]); fields[DAY_OF_YEAR] = dayOfYear; - if(fields[YEAR] == changeYear && gregorianCutover <= timeVal + zoneOffset){ + if (fields[YEAR] == changeYear && gregorianCutover <= time + fields[ZONE_OFFSET]){ dayOfYear += currentYearSkew; } int month = dayOfYear / 32; @@ -493,7 +503,7 @@ public class GregorianCalendar extends Calendar { int dstOffset = fields[YEAR] <= 0 ? 0 : getTimeZone().getOffset(AD, fields[YEAR], month, date, fields[DAY_OF_WEEK], millis); if (fields[YEAR] > 0) { - dstOffset -= zoneOffset; + dstOffset -= fields[ZONE_OFFSET]; } fields[DST_OFFSET] = dstOffset; if (dstOffset != 0) { @@ -507,10 +517,10 @@ public class GregorianCalendar extends Calendar { days++; } if (oldDays != days) { - dayOfYear = computeYearAndDay(days, timeVal - zoneOffset + dayOfYear = computeYearAndDay(days, time - fields[ZONE_OFFSET] + dstOffset); fields[DAY_OF_YEAR] = dayOfYear; - if(fields[YEAR] == changeYear && gregorianCutover <= timeVal - zoneOffset + dstOffset){ + if(fields[YEAR] == changeYear && gregorianCutover <= time - fields[ZONE_OFFSET] + dstOffset){ dayOfYear += currentYearSkew; } month = dayOfYear / 32; @@ -567,10 +577,26 @@ public class GregorianCalendar extends Calendar { TimeZone timeZone = getTimeZone(); int dstOffset = timeZone.inDaylightTime(new Date(time)) ? timeZone.getDSTSavings() : 0; int zoneOffset = timeZone.getRawOffset(); + + // We unconditionally overwrite DST_OFFSET and ZONE_OFFSET with + // values from the timezone that's currently in use. This gives us + // much more consistent behavior, and matches ICU4J behavior (though + // it is inconsistent with the RI). + // + // Anything callers can do with ZONE_OFFSET they can do by constructing + // a SimpleTimeZone with the required offset. + // + // DST_OFFSET is a bit of a WTF, given that it's dependent on the rest + // of the fields. There's no sensible reason we'd want to allow it to + // be set, nor can we implement consistent full-fields calculation after + // this field is set without maintaining a large deal of additional state. + // + // At the very least, we will need isSet to differentiate between fields + // set by the user and fields set by our internal field calculation. fields[DST_OFFSET] = dstOffset; fields[ZONE_OFFSET] = zoneOffset; - fullFieldsCalc(time, zoneOffset); + fullFieldsCalc(); for (int i = 0; i < FIELD_COUNT; i++) { isSet[i] = true; @@ -670,8 +696,16 @@ public class GregorianCalendar extends Calendar { if (useMonth && (lastDateFieldSet == DAY_OF_WEEK || lastDateFieldSet == WEEK_OF_YEAR)) { if (isSet[WEEK_OF_YEAR] && isSet[DAY_OF_WEEK]) { - useMonth = lastDateFieldSet != WEEK_OF_YEAR && weekMonthSet - && isSet[DAY_OF_WEEK]; + if (lastDateFieldSet == WEEK_OF_YEAR) { + useMonth = false; + } else if (lastDateFieldSet == DAY_OF_WEEK) { + // DAY_OF_WEEK belongs to both the Month + Week + Day and the + // WeekOfYear + Day combinations. We're supposed to use the most + // recent combination, as specified by the single set field. We can't + // know for sure in this case, so we always prefer the week-month-day + // combination if week-month is already set. + useMonth = weekMonthSet; + } } else if (isSet[DAY_OF_YEAR]) { useMonth = isSet[DATE] && isSet[MONTH]; } diff --git a/luni/src/main/java/java/util/HashMap.java b/luni/src/main/java/java/util/HashMap.java index 80fbd0c..b6fe646 100644 --- a/luni/src/main/java/java/util/HashMap.java +++ b/luni/src/main/java/java/util/HashMap.java @@ -297,12 +297,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria return e == null ? null : e.value; } - // Doug Lea's supplemental secondaryHash function (inlined). - // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590). - int hash = key.hashCode(); - hash ^= (hash >>> 20) ^ (hash >>> 12); - hash ^= (hash >>> 7) ^ (hash >>> 4); - + int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { @@ -327,12 +322,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria return entryForNullKey != null; } - // Doug Lea's supplemental secondaryHash function (inlined). - // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590). - int hash = key.hashCode(); - hash ^= (hash >>> 20) ^ (hash >>> 12); - hash ^= (hash >>> 7) ^ (hash >>> 4); - + int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { @@ -344,15 +334,6 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria return false; } - // Doug Lea's supplemental secondaryHash function (non-inlined). - // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590). - static int secondaryHash(Object key) { - int hash = key.hashCode(); - hash ^= (hash >>> 20) ^ (hash >>> 12); - hash ^= (hash >>> 7) ^ (hash >>> 4); - return hash; - } - /** * Returns whether this map contains the specified value. * @@ -401,7 +382,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria return putValueForNullKey(value); } - int hash = secondaryHash(key); + int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) { @@ -464,7 +445,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria return; } - int hash = secondaryHash(key); + int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); HashMapEntry<K, V> first = tab[index]; @@ -632,7 +613,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria if (key == null) { return removeNullKey(); } - int hash = secondaryHash(key); + int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index], prev = null; @@ -852,7 +833,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria return e != null && Objects.equal(value, e.value); } - int hash = secondaryHash(key); + int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) { @@ -880,7 +861,7 @@ public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Seria return true; } - int hash = secondaryHash(key); + int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index], prev = null; diff --git a/luni/src/main/java/java/util/IllformedLocaleException.java b/luni/src/main/java/java/util/IllformedLocaleException.java new file mode 100644 index 0000000..3dec1cd --- /dev/null +++ b/luni/src/main/java/java/util/IllformedLocaleException.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 java.util; + +/** + * Thrown when a locale subtag or field is not well formed. + * + * See {@link Locale} and {@link Locale.Builder}. + * + * @since 1.7 + */ +public class IllformedLocaleException extends RuntimeException { + + private final int errorIndex; + + /** + * Constructs a new instance with no detail message and an error index + * of {@code -1}. + */ + public IllformedLocaleException() { + this(null, -1); + } + + /** + * Constructs a new instance with the specified error message. + */ + public IllformedLocaleException(String message) { + this(message, -1); + } + + /** + * Constructs a new instance with the specified error message and + * error index. + */ + public IllformedLocaleException(String message, int errorIndex) { + super(message); + this.errorIndex = errorIndex; + } + + public int getErrorIndex() { + return errorIndex; + } +} diff --git a/luni/src/main/java/java/util/LinkedHashMap.java b/luni/src/main/java/java/util/LinkedHashMap.java index e61b0f9..3d6e6c3 100644 --- a/luni/src/main/java/java/util/LinkedHashMap.java +++ b/luni/src/main/java/java/util/LinkedHashMap.java @@ -74,7 +74,7 @@ public class LinkedHashMap<K, V> extends HashMap<K, V> { * * @param initialCapacity * the initial capacity of this map. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * when the capacity is less than zero. */ public LinkedHashMap(int initialCapacity) { @@ -247,8 +247,7 @@ public class LinkedHashMap<K, V> extends HashMap<K, V> { return e.value; } - // Replace with Collections.secondaryHash when the VM is fast enough (http://b/8290590). - int hash = secondaryHash(key); + int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java index fc8f7c6..a6368e8 100644 --- a/luni/src/main/java/java/util/Locale.java +++ b/luni/src/main/java/java/util/Locale.java @@ -22,6 +22,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamField; import java.io.Serializable; +import java.nio.charset.StandardCharsets; import libcore.icu.ICU; /** @@ -40,7 +41,7 @@ import libcore.icu.ICU; * rewriting happens even if you construct your own {@code Locale} object, not just for * instances returned by the various lookup methods. * - * <a name="available_locales"><h3>Available locales</h3></a> + * <a name="available_locales"></a><h3>Available locales</h3> * <p>This class' constructors do no error checking. You can create a {@code Locale} for languages * and countries that don't exist, and you can create instances for combinations that don't * exist (such as "de_US" for "German as spoken in the US"). @@ -59,7 +60,7 @@ import libcore.icu.ICU; * device you're running on, or {@link Locale#getAvailableLocales} to get a list of all the locales * available on the device you're running on. * - * <a name="locale_data"><h3>Locale data</h3></a> + * <a name="locale_data"></a><h3>Locale data</h3> * <p>Note that locale data comes solely from ICU. User-supplied locale service providers (using * the {@code java.text.spi} or {@code java.util.spi} mechanisms) are not supported. * @@ -79,24 +80,28 @@ import libcore.icu.ICU; * <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-8">CLDR 1.8</a></td> * <td><a href="http://www.unicode.org/versions/Unicode5.2.0/">Unicode 5.2</a></td></tr> * <tr><td>Android 4.0 (Ice Cream Sandwich)</td> - * <td>ICU 4.6</td> + * <td><a href="http://site.icu-project.org/download/46">ICU 4.6</a></td> * <td><a href="http://cldr.unicode.org/index/downloads/cldr-1-9">CLDR 1.9</a></td> * <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr> * <tr><td>Android 4.1 (Jelly Bean)</td> - * <td>ICU 4.8</td> + * <td><a href="http://site.icu-project.org/download/48">ICU 4.8</a></td> * <td><a href="http://cldr.unicode.org/index/downloads/cldr-2-0">CLDR 2.0</a></td> * <td><a href="http://www.unicode.org/versions/Unicode6.0.0/">Unicode 6.0</a></td></tr> * <tr><td>Android 4.3 (Jelly Bean MR2)</td> - * <td>ICU 50</td> + * <td><a href="http://site.icu-project.org/download/50">ICU 50</a></td> * <td><a href="http://cldr.unicode.org/index/downloads/cldr-22-1">CLDR 22.1</a></td> * <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr> * <tr><td>Android 4.4 (KitKat)</td> - * <td>ICU 51</td> + * <td><a href="http://site.icu-project.org/download/51">ICU 51</a></td> * <td><a href="http://cldr.unicode.org/index/downloads/cldr-23">CLDR 23</a></td> * <td><a href="http://www.unicode.org/versions/Unicode6.2.0/">Unicode 6.2</a></td></tr> + * <tr><td>Android 5.0 (Lollipop)</td> + * <td><a href="http://site.icu-project.org/download/53">ICU 53</a></td> + * <td><a href="http://cldr.unicode.org/index/downloads/cldr-25">CLDR 25</a></td> + * <td><a href="http://www.unicode.org/versions/Unicode6.3.0/">Unicode 6.3</a></td></tr> * </table> * - * <a name="default_locale"><h3>Be wary of the default locale</h3></a> + * <a name="default_locale"></a><h3>Be wary of the default locale</h3> * <p>Note that there are many convenience methods that automatically use the default locale, but * using them may lead to subtle bugs. * @@ -243,6 +248,31 @@ public final class Locale implements Cloneable, Serializable { public static final Locale US = new Locale(true, "en", "US"); /** + * BCP-47 extension identifier (or "singleton") for the private + * use extension. + * + * See {@link #getExtension(char)} and {@link Builder#setExtension(char, String)}. + * + * @since 1.7 + */ + public static final char PRIVATE_USE_EXTENSION = 'x'; + + /** + * BCP-47 extension identifier (or "singleton") for the unicode locale extension. + * + * + * See {@link #getExtension(char)} and {@link Builder#setExtension(char, String)}. + * + * @since 1.7 + */ + public static final char UNICODE_LOCALE_EXTENSION = 'u'; + + /** + * ISO 639-3 generic code for undetermined languages. + */ + 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. */ @@ -255,70 +285,663 @@ public final class Locale implements Cloneable, Serializable { defaultLocale = new Locale(language, region, variant); } + /** + * A class that helps construct {@link Locale} instances. + * + * Unlike the public {@code Locale} constructors, the methods of this class + * perform much stricter checks on their input. + * + * Validity checks on the {@code language}, {@code country}, {@code variant} + * and {@code extension} values are carried out as per the + * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> specification. + * + * In addition, we treat the <a href="http://www.unicode.org/reports/tr35/"> + * Unicode locale extension</a> specially and provide methods to manipulate + * the structured state (keywords and attributes) specified therein. + * + * @since 1.7 + */ + public static final class Builder { + private String language; + private String region; + private String variant; + private String script; + + private final Set<String> attributes; + private final Map<String, String> keywords; + private final Map<Character, String> extensions; + + public Builder() { + language = region = variant = script = ""; + + // NOTE: We use sorted maps in the builder & the locale class itself + // because serialized forms of the unicode locale extension (and + // of the extension map itself) are specified to be in alphabetic + // order of keys. + attributes = new TreeSet<String>(); + keywords = new TreeMap<String, String>(); + extensions = new TreeMap<Character, String>(); + } + + /** + * Sets the locale language. If {@code language} is {@code null} or empty, the + * previous value is cleared. + * + * As per BCP-47, the language must be between 2 and 3 ASCII characters + * in length and must only contain characters in the range {@code [a-zA-Z]}. + * + * This value is usually an <a href="http://www.loc.gov/standards/iso639-2/"> + * ISO-639-2</a> alpha-2 or alpha-3 code, though no explicit checks are + * carried out that it's a valid code in that namespace. + * + * Values are normalized to lower case. + * + * Note that we don't support BCP-47 "extlang" languages because they were + * only ever used to substitute for a lack of 3 letter language codes. + * + * @throws IllformedLocaleException if the language was invalid. + */ + public Builder setLanguage(String language) { + this.language = normalizeAndValidateLanguage(language, true /* strict */); + return this; + } + + private static String normalizeAndValidateLanguage(String language, boolean strict) { + if (language == null || language.isEmpty()) { + return ""; + } + + final String lowercaseLanguage = language.toLowerCase(Locale.ROOT); + if (!isValidBcp47Alpha(lowercaseLanguage, 2, 3)) { + if (strict) { + throw new IllformedLocaleException("Invalid language: " + language); + } else { + return UNDETERMINED_LANGUAGE; + } + } + + return lowercaseLanguage; + } + + /** + * Set the state of this builder to the parsed contents of the BCP-47 language + * tag {@code languageTag}. + * + * This method is equivalent to a call to {@link #clear} if {@code languageTag} + * is {@code null} or empty. + * + * <b>NOTE:</b> In contrast to {@link Locale#forLanguageTag(String)}, which + * simply ignores malformed input, this method will throw an exception if + * its input is malformed. + * + * @throws IllformedLocaleException if {@code languageTag} is not a well formed + * BCP-47 tag. + */ + public Builder setLanguageTag(String languageTag) { + if (languageTag == null || languageTag.isEmpty()) { + clear(); + return this; + } + + final Locale fromIcu = forLanguageTag(languageTag, true /* strict */); + // When we ask ICU for strict parsing, it might return a null locale + // if the language tag is malformed. + if (fromIcu == null) { + throw new IllformedLocaleException("Invalid languageTag: " + languageTag); + } + + setLocale(fromIcu); + return this; + } + + /** + * Sets the locale region. If {@code region} is {@code null} or empty, the + * previous value is cleared. + * + * As per BCP-47, the region must either be a 2 character ISO-3166-1 code + * (each character in the range [a-zA-Z]) OR a 3 digit UN M.49 code. + * + * Values are normalized to upper case. + * + * @throws IllformedLocaleException if {@code} region is invalid. + */ + public Builder setRegion(String region) { + this.region = normalizeAndValidateRegion(region, true /* strict */); + return this; + } + + private static String normalizeAndValidateRegion(String region, boolean strict) { + if (region == null || region.isEmpty()) { + return ""; + } + + final String uppercaseRegion = region.toUpperCase(Locale.ROOT); + if (!isValidBcp47Alpha(uppercaseRegion, 2, 2) && + !isUnM49AreaCode(uppercaseRegion)) { + if (strict) { + throw new IllformedLocaleException("Invalid region: " + region); + } else { + return ""; + } + } + + return uppercaseRegion; + } + + /** + * Sets the locale variant. If {@code variant} is {@code null} or empty, + * the previous value is cleared. + * + * The input string my consist of one or more variants separated by + * valid separators ('-' or '_'). + * + * As per BCP-47, each variant must be between 5 and 8 alphanumeric characters + * in length (each character in the range {@code [a-zA-Z0-9]}) but + * can be exactly 4 characters in length if the first character is a digit. + * + * Note that this is a much stricter interpretation of {@code variant} + * than the public {@code Locale} constructors. The latter allowed free form + * variants. + * + * Variants are case sensitive and all separators are normalized to {@code '_'}. + * + * @throws IllformedLocaleException if {@code} variant is invalid. + */ + public Builder setVariant(String variant) { + this.variant = normalizeAndValidateVariant(variant); + return this; + } + + private static String normalizeAndValidateVariant(String variant) { + if (variant == null || variant.isEmpty()) { + return ""; + } + + // Note that unlike extensions, we canonicalize to lower case alphabets + // and underscores instead of hyphens. + final String normalizedVariant = variant.replace('-', '_'); + String[] subTags = normalizedVariant.split("_"); + + for (String subTag : subTags) { + if (!isValidVariantSubtag(subTag)) { + throw new IllformedLocaleException("Invalid variant: " + variant); + } + } + + return normalizedVariant; + } + + private static boolean isValidVariantSubtag(String subTag) { + // The BCP-47 spec states that : + // - Subtags can be between [5, 8] alphanumeric chars in length. + // - Subtags that start with a number are allowed to be 4 chars in length. + if (subTag.length() >= 5 && subTag.length() <= 8) { + if (isAsciiAlphaNum(subTag)) { + return true; + } + } else if (subTag.length() == 4) { + final char firstChar = subTag.charAt(0); + if ((firstChar >= '0' && firstChar <= '9') && isAsciiAlphaNum(subTag)) { + return true; + } + } + + return false; + } + + /** + * Sets the locale script. If {@code script} is {@code null} or empty, + * the previous value is cleared. + * + * As per BCP-47, the script must be 4 characters in length, and + * each character in the range {@code [a-zA-Z]}. + * + * A script usually represents a valid ISO 15924 script code, though no + * other registry or validity checks are performed. + * + * Scripts are normalized to title cased values. + * + * @throws IllformedLocaleException if {@code script} is invalid. + */ + public Builder setScript(String script) { + this.script = normalizeAndValidateScript(script, true /* strict */); + return this; + } + + private static String normalizeAndValidateScript(String script, boolean strict) { + if (script == null || script.isEmpty()) { + return ""; + } + + if (!isValidBcp47Alpha(script, 4, 4)) { + if (strict) { + throw new IllformedLocaleException("Invalid script: " + script); + } else { + return ""; + } + } + + return titleCaseAsciiWord(script); + } + + /** + * Sets the state of the builder to the {@link Locale} represented by + * {@code locale}. + * + * Note that the locale's language, region and variant are validated as per + * the rules specified in {@link #setLanguage}, {@link #setRegion} and + * {@link #setVariant}. + * + * All existing builder state is discarded. + * + * @throws IllformedLocaleException if {@code locale} is invalid. + * @throws NullPointerException if {@code locale} is null. + */ + public Builder setLocale(Locale locale) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + + // Make copies of the existing values so that we don't partially + // update the state if we encounter an error. + final String backupLanguage = language; + final String backupRegion = region; + final String backupVariant = variant; + + try { + setLanguage(locale.getLanguage()); + setRegion(locale.getCountry()); + setVariant(locale.getVariant()); + } catch (IllformedLocaleException ifle) { + language = backupLanguage; + region = backupRegion; + variant = backupVariant; + + throw ifle; + } + + // The following values can be set only via the builder class, so + // there's no need to normalize them or check their validity. + + this.script = locale.getScript(); + + extensions.clear(); + extensions.putAll(locale.extensions); + + keywords.clear(); + keywords.putAll(locale.unicodeKeywords); + + attributes.clear(); + attributes.addAll(locale.unicodeAttributes); + + return this; + } + + /** + * Adds the specified attribute to the list of attributes in the unicode + * locale extension. + * + * Attributes must be between 3 and 8 characters in length, and each character + * must be in the range {@code [a-zA-Z0-9]}. + * + * Attributes are normalized to lower case values. All added attributes and + * keywords are combined to form a complete unicode locale extension on + * {@link Locale} objects built by this builder, and accessible via + * {@link Locale#getExtension(char)} with the {@link Locale#UNICODE_LOCALE_EXTENSION} + * key. + * + * @throws IllformedLocaleException if {@code attribute} is invalid. + * @throws NullPointerException if {@code attribute} is null. + */ + public Builder addUnicodeLocaleAttribute(String attribute) { + if (attribute == null) { + throw new NullPointerException("attribute == null"); + } + + final String lowercaseAttribute = attribute.toLowerCase(Locale.ROOT); + if (!isValidBcp47Alphanum(lowercaseAttribute, 3, 8)) { + throw new IllformedLocaleException("Invalid locale attribute: " + attribute); + } + + attributes.add(lowercaseAttribute); + + return this; + } + + /** + * Removes an attribute from the list of attributes in the unicode locale + * extension. + * + * {@code attribute} must be valid as per the rules specified in + * {@link #addUnicodeLocaleAttribute}. + * + * This method has no effect if {@code attribute} hasn't already been + * added. + * + * @throws IllformedLocaleException if {@code attribute} is invalid. + * @throws NullPointerException if {@code attribute} is null. + */ + public Builder removeUnicodeLocaleAttribute(String attribute) { + if (attribute == null) { + throw new NullPointerException("attribute == null"); + } + + // Weirdly, remove is specified to check whether the attribute + // is valid, so we have to perform the full alphanumeric check here. + final String lowercaseAttribute = attribute.toLowerCase(Locale.ROOT); + if (!isValidBcp47Alphanum(lowercaseAttribute, 3, 8)) { + throw new IllformedLocaleException("Invalid locale attribute: " + attribute); + } + + attributes.remove(attribute); + return this; + } + + /** + * Sets the extension identified by {@code key} to {@code value}. + * + * {@code key} must be in the range {@code [a-zA-Z0-9]}. + * + * If {@code value} is {@code null} or empty, the extension is removed. + * + * In the general case, {@code value} must be a series of subtags separated + * by ({@code "-"} or {@code "_"}). Each subtag must be between + * 2 and 8 characters in length, and each character in the subtag must be in + * the range {@code [a-zA-Z0-9]}. + * + * <p> + * There are two special cases : + * <li> + * <ul> + * The unicode locale extension + * ({@code key == 'u'}, {@link Locale#UNICODE_LOCALE_EXTENSION}) : Setting + * the unicode locale extension results in all existing keyword and attribute + * state being replaced by the parsed result of {@code value}. For example, + * {@code builder.setExtension('u', "baaaz-baaar-fo-baar-ba-baaz")} + * is equivalent to: + * <pre> + * builder.addUnicodeLocaleAttribute("baaaz"); + * builder.addUnicodeLocaleAttribute("baaar"); + * builder.setUnicodeLocaleKeyword("fo", "baar"); + * builder.setUnicodeLocaleKeyword("ba", "baaa"); + * </pre> + * </ul> + * <ul> + * The private use extension + * ({@code key == 'x'}, {@link Locale#PRIVATE_USE_EXTENSION}) : Each subtag in a + * private use extension can be between 1 and 8 characters in length (in contrast + * to a minimum length of 2 for all other extensions). + * </ul> + * </li> + * + * @throws IllformedLocaleException if {@code value} is invalid. + */ + public Builder setExtension(char key, String value) { + if (value == null || value.isEmpty()) { + extensions.remove(key); + return this; + } + + final String normalizedValue = value.toLowerCase(Locale.ROOT).replace('_', '-'); + final String[] subtags = normalizedValue.split("-"); + + // Lengths for subtags in the private use extension should be [1, 8] chars. + // For all other extensions, they should be [2, 8] chars. + // + // http://www.rfc-editor.org/rfc/bcp/bcp47.txt + final int minimumLength = (key == PRIVATE_USE_EXTENSION) ? 1 : 2; + for (String subtag : subtags) { + if (!isValidBcp47Alphanum(subtag, minimumLength, 8)) { + throw new IllformedLocaleException( + "Invalid private use extension : " + value); + } + } + + // We need to take special action in the case of unicode extensions, + // since we claim to understand their keywords and attributes. + if (key == UNICODE_LOCALE_EXTENSION) { + // First clear existing attributes and keywords. + extensions.clear(); + attributes.clear(); + + parseUnicodeExtension(subtags, keywords, attributes); + } else { + extensions.put(key, normalizedValue); + } + + return this; + } + + /** + * Clears all extensions from this builder. Note that this also implicitly + * clears all state related to the unicode locale extension; all attributes + * and keywords set by {@link #addUnicodeLocaleAttribute} and + * {@link #setUnicodeLocaleKeyword} are cleared. + */ + public Builder clearExtensions() { + extensions.clear(); + attributes.clear(); + keywords.clear(); + return this; + } + + /** + * Adds a key / type pair to the list of unicode locale extension keys. + * + * {@code key} must be 2 characters in length, and each character must be + * in the range {@code [a-zA-Z0-9]}. + * + * {#code type} can either be empty, or a series of one or more subtags + * separated by a separator ({@code "-"} or {@code "_"}). Each subtag must + * be between 3 and 8 characters in length and each character in the subtag + * must be in the range {@code [a-zA-Z0-9]}. + * + * Note that the type is normalized to lower case, and all separators + * are normalized to {@code "-"}. All added attributes and + * keywords are combined to form a complete unicode locale extension on + * {@link Locale} objects built by this builder, and accessible via + * {@link Locale#getExtension(char)} with the {@link Locale#UNICODE_LOCALE_EXTENSION} + * key. + * + * @throws IllformedLocaleException if {@code key} or {@code value} are + * invalid. + */ + public Builder setUnicodeLocaleKeyword(String key, String type) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + if (type == null && keywords != null) { + keywords.remove(key); + return this; + } + + final String lowerCaseKey = key.toLowerCase(Locale.ROOT); + // The key must be exactly two alphanumeric characters. + if (lowerCaseKey.length() != 2 || !isAsciiAlphaNum(lowerCaseKey)) { + throw new IllformedLocaleException("Invalid unicode locale keyword: " + key); + } + + // The type can be one or more alphanumeric strings of length [3, 8] characters, + // separated by a separator char, which is one of "_" or "-". Though the spec + // doesn't require it, we normalize all "_" to "-" to make the rest of our + // processing easier. + final String lowerCaseType = type.toLowerCase(Locale.ROOT).replace("_", "-"); + if (!isValidTypeList(lowerCaseType)) { + throw new IllformedLocaleException("Invalid unicode locale type: " + type); + } + + // Everything checks out fine, add the <key, type> mapping to the list. + keywords.put(lowerCaseKey, lowerCaseType); + + return this; + } + + /** + * Clears all existing state from this builder. + */ + public Builder clear() { + clearExtensions(); + language = region = variant = script = ""; + + return this; + } + + /** + * Constructs a locale from the existing state of the builder. Note that this + * method is guaranteed to succeed since field validity checks are performed + * at the point of setting them. + */ + public Locale build() { + // NOTE: We need to make a copy of attributes, keywords and extensions + // because the RI allows this builder to reused. + return new Locale(language, region, variant, script, + attributes, keywords, extensions, + true /* has validated fields */); + } + } + + /** + * Returns a locale for a given BCP-47 language tag. This method is more + * lenient than {@link Builder#setLanguageTag}. For a given language tag, parsing + * will proceed up to the first malformed subtag. All subsequent tags are discarded. + * Note that language tags use {@code -} rather than {@code _}, for example {@code en-US}. + * + * @throws NullPointerException if {@code languageTag} is {@code null}. + * + * @since 1.7 + */ + public static Locale forLanguageTag(String languageTag) { + if (languageTag == null) { + throw new NullPointerException("languageTag == null"); + } + + return forLanguageTag(languageTag, false /* strict */); + } + private transient String countryCode; private transient String languageCode; private transient String variantCode; + private transient String scriptCode; + + /* Sorted, Unmodifiable */ + private transient Set<String> unicodeAttributes; + /* Sorted, Unmodifiable */ + private transient Map<String, String> unicodeKeywords; + /* Sorted, Unmodifiable */ + private transient Map<Character, String> extensions; + + /** + * Whether this instance was constructed from a builder. We can make + * stronger assumptions about the validity of Locale fields if this was + * constructed by a builder. + */ + private transient final boolean hasValidatedFields; + private transient String cachedToStringResult; + private transient String cachedLanguageTag; + private transient String cachedIcuLocaleId; /** * There's a circular dependency between toLowerCase/toUpperCase and * Locale.US. Work around this by avoiding these methods when constructing * the built-in locales. - * - * @param unused required for this constructor to have a unique signature */ - private Locale(boolean unused, String lowerCaseLanguageCode, String upperCaseCountryCode) { + private Locale(boolean hasValidatedFields, String lowerCaseLanguageCode, + String upperCaseCountryCode) { this.languageCode = lowerCaseLanguageCode; this.countryCode = upperCaseCountryCode; this.variantCode = ""; + this.scriptCode = ""; + + this.unicodeAttributes = Collections.EMPTY_SET; + this.unicodeKeywords = Collections.EMPTY_MAP; + this.extensions = Collections.EMPTY_MAP; + + this.hasValidatedFields = hasValidatedFields; } /** * Constructs a new {@code Locale} using the specified language. */ public Locale(String language) { - this(language, "", ""); + this(language, "", "", "", Collections.EMPTY_SET, Collections.EMPTY_MAP, + Collections.EMPTY_MAP, false /* has validated fields */); } /** * Constructs a new {@code Locale} using the specified language and country codes. */ public Locale(String language, String country) { - this(language, country, ""); + this(language, country, "", "", Collections.EMPTY_SET, Collections.EMPTY_MAP, + Collections.EMPTY_MAP, false /* has validated fields */); } /** - * Constructs a new {@code Locale} using the specified language, country, - * and variant codes. + * Required by libcore.icu.ICU. + * + * @hide */ - public Locale(String language, String country, String variant) { + public Locale(String language, String country, String variant, String scriptCode, + /* nonnull */ Set<String> unicodeAttributes, + /* nonnull */ Map<String, String> unicodeKeywords, + /* nonnull */ Map<Character, String> extensions, + boolean hasValidatedFields) { if (language == null || country == null || variant == null) { throw new NullPointerException("language=" + language + - ",country=" + country + - ",variant=" + variant); + ",country=" + country + + ",variant=" + variant); } - if (language.isEmpty() && country.isEmpty()) { - languageCode = ""; - countryCode = ""; - variantCode = variant; - return; + + if (hasValidatedFields) { + this.languageCode = adjustLanguageCode(language); + this.countryCode = country; + this.variantCode = variant; + } else { + if (language.isEmpty() && country.isEmpty()) { + languageCode = ""; + countryCode = ""; + variantCode = variant; + } else { + languageCode = adjustLanguageCode(language); + countryCode = country.toUpperCase(Locale.US); + variantCode = variant; + } } - languageCode = language.toLowerCase(Locale.US); - // Map new language codes to the obsolete language - // codes so the correct resource bundles will be used. - if (languageCode.equals("he")) { - languageCode = "iw"; - } else if (languageCode.equals("id")) { - languageCode = "in"; - } else if (languageCode.equals("yi")) { - languageCode = "ji"; + this.scriptCode = scriptCode; + + if (hasValidatedFields) { + Set<String> attribsCopy = new TreeSet<String>(unicodeAttributes); + Map<String, String> keywordsCopy = new TreeMap<String, String>(unicodeKeywords); + Map<Character, String> extensionsCopy = new TreeMap<Character, String>(extensions); + + // We need to transform the list of attributes & keywords set on the + // builder to a unicode locale extension. i.e, if we have any keywords + // or attributes set, Locale#getExtension('u') should return a well + // formed extension. + addUnicodeExtensionToExtensionsMap(attribsCopy, keywordsCopy, extensionsCopy); + + this.unicodeAttributes = Collections.unmodifiableSet(attribsCopy); + this.unicodeKeywords = Collections.unmodifiableMap(keywordsCopy); + this.extensions = Collections.unmodifiableMap(extensionsCopy); + } else { + this.unicodeAttributes = unicodeAttributes; + this.unicodeKeywords = unicodeKeywords; + this.extensions = extensions; } - countryCode = country.toUpperCase(Locale.US); + this.hasValidatedFields = hasValidatedFields; + } - // Work around for be compatible with RI - variantCode = variant; + /** + * Constructs a new {@code Locale} using the specified language, country, + * and variant codes. + */ + public Locale(String language, String country, String variant) { + this(language, country, variant, "", Collections.EMPTY_SET, + Collections.EMPTY_MAP, Collections.EMPTY_MAP, + false /* has validated fields */); } @Override public Object clone() { @@ -341,7 +964,10 @@ public final class Locale implements Cloneable, Serializable { Locale o = (Locale) object; return languageCode.equals(o.languageCode) && countryCode.equals(o.countryCode) - && variantCode.equals(o.variantCode); + && variantCode.equals(o.variantCode) + && scriptCode.equals(o.scriptCode) + && extensions.equals(o.extensions); + } return false; } @@ -399,9 +1025,16 @@ public final class Locale implements Cloneable, Serializable { if (countryCode.isEmpty()) { return ""; } - String result = ICU.getDisplayCountryNative(toString(), locale.toString()); + + final String normalizedRegion = Builder.normalizeAndValidateRegion( + countryCode, false /* strict */); + if (normalizedRegion.isEmpty()) { + return countryCode; + } + + String result = ICU.getDisplayCountry(this, locale); if (result == null) { // TODO: do we need to do this, or does ICU do it for us? - result = ICU.getDisplayCountryNative(toString(), Locale.getDefault().toString()); + result = ICU.getDisplayCountry(this, Locale.getDefault()); } return result; } @@ -422,17 +1055,24 @@ public final class Locale implements Cloneable, Serializable { return ""; } - // http://b/8049507 --- frameworks/base should use fil_PH instead of tl_PH. - // Until then, we're stuck covering their tracks, making it look like they're - // using "fil" when they're not. - String localeString = toString(); - if (languageCode.equals("tl")) { - localeString = toNewString("fil", countryCode, variantCode); + // Hacks for backward compatibility. + // + // Our language tag will contain "und" if the languageCode is invalid + // or missing. ICU will then return "langue indéterminée" or the equivalent + // display language for the indeterminate language code. + // + // Sigh... ugh... and what not. + final String normalizedLanguage = Builder.normalizeAndValidateLanguage( + languageCode, false /* strict */); + if (UNDETERMINED_LANGUAGE.equals(normalizedLanguage)) { + return languageCode; } - String result = ICU.getDisplayLanguageNative(localeString, locale.toString()); + // TODO: We need a new hack or a complete fix for http://b/8049507 --- We would + // cover the frameworks' tracks when they were using "tl" instead of "fil". + String result = ICU.getDisplayLanguage(this, locale); if (result == null) { // TODO: do we need to do this, or does ICU do it for us? - result = ICU.getDisplayLanguageNative(localeString, Locale.getDefault().toString()); + result = ICU.getDisplayLanguage(this, Locale.getDefault()); } return result; } @@ -447,13 +1087,14 @@ public final class Locale implements Cloneable, Serializable { /** * Returns this locale's language name, country name, and variant, localized * to {@code locale}. The exact output form depends on whether this locale - * corresponds to a specific language, country and variant. + * corresponds to a specific language, script, country and variant. * * <p>For example: * <ul> * <li>{@code new Locale("en").getDisplayName(Locale.US)} -> {@code English} * <li>{@code new Locale("en", "US").getDisplayName(Locale.US)} -> {@code English (United States)} * <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.US)} -> {@code English (United States,Computer)} + * <li>{@code Locale.fromLanguageTag("zh-Hant-CN").getDisplayName(Locale.US)} -> {@code Chinese (Traditional Han,China)} * <li>{@code new Locale("en").getDisplayName(Locale.FRANCE)} -> {@code anglais} * <li>{@code new Locale("en", "US").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis)} * <li>{@code new Locale("en", "US", "POSIX").getDisplayName(Locale.FRANCE)} -> {@code anglais (États-Unis,informatique)}. @@ -467,9 +1108,19 @@ public final class Locale implements Cloneable, Serializable { buffer.append(displayLanguage.isEmpty() ? languageCode : displayLanguage); ++count; } + if (!scriptCode.isEmpty()) { + if (count == 1) { + buffer.append(" ("); + } + String displayScript = getDisplayScript(locale); + buffer.append(displayScript.isEmpty() ? scriptCode : displayScript); + ++count; + } if (!countryCode.isEmpty()) { if (count == 1) { buffer.append(" ("); + } else if (count == 2) { + buffer.append(","); } String displayCountry = getDisplayCountry(locale); buffer.append(displayCountry.isEmpty() ? countryCode : displayCountry); @@ -478,7 +1129,7 @@ public final class Locale implements Cloneable, Serializable { if (!variantCode.isEmpty()) { if (count == 1) { buffer.append(" ("); - } else if (count == 2) { + } else if (count == 2 || count == 3) { buffer.append(","); } String displayVariant = getDisplayVariant(locale); @@ -495,6 +1146,8 @@ public final class Locale implements Cloneable, Serializable { * Returns the full variant name in the default {@code Locale} for the variant code of * this {@code Locale}. If there is no matching variant name, the variant code is * returned. + * + * @since 1.7 */ public final String getDisplayVariant() { return getDisplayVariant(getDefault()); @@ -504,14 +1157,31 @@ public final class Locale implements Cloneable, Serializable { * Returns the full variant name in the specified {@code Locale} for the variant code * of this {@code Locale}. If there is no matching variant name, the variant code is * returned. + * + * @since 1.7 */ public String getDisplayVariant(Locale locale) { - if (variantCode.length() == 0) { + if (variantCode.isEmpty()) { + return ""; + } + + try { + Builder.normalizeAndValidateVariant(variantCode); + } catch (IllformedLocaleException ilfe) { return variantCode; } - String result = ICU.getDisplayVariantNative(toString(), locale.toString()); + + String result = ICU.getDisplayVariant(this, locale); if (result == null) { // TODO: do we need to do this, or does ICU do it for us? - result = ICU.getDisplayVariantNative(toString(), Locale.getDefault().toString()); + result = ICU.getDisplayVariant(this, Locale.getDefault()); + } + + // The "old style" locale constructors allow us to pass in variants that aren't + // valid BCP-47 variant subtags. When that happens, toLanguageTag will not emit + // them. Note that we know variantCode.length() > 0 due to the isEmpty check at + // the beginning of this function. + if (result.isEmpty()) { + return variantCode; } return result; } @@ -522,7 +1192,10 @@ public final class Locale implements Cloneable, Serializable { * @throws MissingResourceException if there's no 3-letter country code for this locale. */ public String getISO3Country() { - String code = ICU.getISO3CountryNative(toString()); + // The results of getISO3Country do not depend on the languageCode, + // so we pass an arbitrarily selected language code here. This guards + // against errors caused by malformed or invalid language codes. + String code = ICU.getISO3Country("en-" + countryCode); if (!countryCode.isEmpty() && code.isEmpty()) { throw new MissingResourceException("No 3-letter country code for locale: " + this, "FormatData_" + this, "ShortCountry"); } @@ -535,7 +1208,16 @@ public final class Locale implements Cloneable, Serializable { * @throws MissingResourceException if there's no 3-letter language code for this locale. */ public String getISO3Language() { - String code = ICU.getISO3LanguageNative(toString()); + // For backward compatibility, we must return "" for an empty language + // code and not "und" which is the accurate ISO-639-3 code for an + // undetermined language. + if (languageCode.isEmpty()) { + return ""; + } + + // The results of getISO3Language do not depend on the country code + // or any of the other locale fields, so we pass just the language here. + String code = ICU.getISO3Language(languageCode); if (!languageCode.isEmpty() && code.isEmpty()) { throw new MissingResourceException("No 3-letter language code for locale: " + this, "FormatData_" + this, "ShortLanguage"); } @@ -574,10 +1256,335 @@ public final class Locale implements Cloneable, Serializable { return variantCode; } + /** + * Returns the script code for this {@code Locale} or an empty {@code String} if no script + * was set. + * + * If set, the script code will be a title cased string of length 4, as per the ISO 15924 + * specification. + * + * @since 1.7 + */ + public String getScript() { + return scriptCode; + } + + /** + * Equivalent to {@code getDisplayScript(Locale.getDefault()))} + * + * @since 1.7 + */ + public String getDisplayScript() { + return getDisplayScript(getDefault()); + } + + /** + * Returns the name of this locale's script code, localized to {@link Locale}. If the + * script code is unknown, the return value of this method is the same as that of + * {@link #getScript()}. + * + * @since 1.7 + */ + public String getDisplayScript(Locale locale) { + if (scriptCode.isEmpty()) { + return ""; + } + + String result = ICU.getDisplayScript(this, locale); + if (result == null) { // TODO: do we need to do this, or does ICU do it for us? + result = ICU.getDisplayScript(this, Locale.getDefault()); + } + + return result; + + } + + /** + * Returns a well formed BCP-47 language tag that identifies this locale. + * + * Note that this locale itself might consist of ill formed fields, since the + * public {@code Locale} constructors do not perform validity checks to maintain + * backwards compatibility. When this is the case, this method will either replace + * ill formed fields with standard BCP-47 subtags (For eg. "und" (undetermined) + * for invalid languages) or omit them altogether. + * + * Additionally, ill formed variants will result in the remainder of the tag + * (both variants and extensions) being moved to the private use extension, + * where they will appear after a subtag whose value is {@code "lvariant"}. + * + * It's also important to note that the BCP-47 tag is well formed in the sense + * that it is unambiguously parseable into its specified components. We do not + * require that any of the components are registered with the applicable registries. + * For example, we do not require scripts to be a registered ISO 15924 scripts or + * languages to appear in the ISO-639-2 code list. + * + * @since 1.7 + */ + public String toLanguageTag() { + if (cachedLanguageTag == null) { + cachedLanguageTag = makeLanguageTag(); + } + + return cachedLanguageTag; + } + + /** + * Constructs a valid BCP-47 language tag from locale fields. Additional validation + * is required when this Locale was not constructed using a Builder and variants + * set this way are treated specially. + * + * In both cases, we convert empty language tags to "und", omit invalid country tags + * and perform a special case conversion of "no-NO-NY" to "nn-NO". + */ + private String makeLanguageTag() { + // We only need to revalidate the language, country and variant because + // the rest of the fields can only be set via the builder which validates + // them anyway. + String language = ""; + String region = ""; + String variant = ""; + String illFormedVariantSubtags = ""; + + if (hasValidatedFields) { + language = languageCode; + region = countryCode; + // Note that we are required to normalize hyphens to underscores + // in the builder, but we must use hyphens in the BCP-47 language tag. + variant = variantCode.replace('_', '-'); + } else { + language = Builder.normalizeAndValidateLanguage(languageCode, false /* strict */); + region = Builder.normalizeAndValidateRegion(countryCode, false /* strict */); + + try { + variant = Builder.normalizeAndValidateVariant(variantCode); + } catch (IllformedLocaleException ilfe) { + // If our variant is ill formed, we must attempt to split it into + // its constituent subtags and preserve the well formed bits and + // move the rest to the private use extension (if they're well + // formed extension subtags). + String split[] = splitIllformedVariant(variantCode); + + variant = split[0]; + illFormedVariantSubtags = split[1]; + } + } + + if (language.isEmpty()) { + language = UNDETERMINED_LANGUAGE; + } + + if ("no".equals(language) && "NO".equals(region) && "NY".equals(variant)) { + language = "nn"; + region = "NO"; + variant = ""; + } + + final StringBuilder sb = new StringBuilder(16); + sb.append(language); + + if (!scriptCode.isEmpty()) { + sb.append('-'); + sb.append(scriptCode); + } + + if (!region.isEmpty()) { + sb.append('-'); + sb.append(region); + } + + if (!variant.isEmpty()) { + sb.append('-'); + sb.append(variant); + } + + // Extensions (optional, omitted if empty). Note that we don't + // emit the private use extension here, but add it in the end. + for (Map.Entry<Character, String> extension : extensions.entrySet()) { + if (!extension.getKey().equals('x')) { + sb.append('-').append(extension.getKey()); + sb.append('-').append(extension.getValue()); + } + } + + // The private use extension comes right at the very end. + final String privateUse = extensions.get('x'); + if (privateUse != null) { + sb.append("-x-"); + sb.append(privateUse); + } + + // If we have any ill-formed variant subtags, we append them to the + // private use extension (or add a private use extension if one doesn't + // exist). + if (!illFormedVariantSubtags.isEmpty()) { + if (privateUse == null) { + sb.append("-x-lvariant-"); + } else { + sb.append('-'); + } + sb.append(illFormedVariantSubtags); + } + + return sb.toString(); + } + + /** + * Splits ill formed variants into a set of valid variant subtags (which + * can be used directly in language tag construction) and a set of invalid + * variant subtags (which can be appended to the private use extension), + * provided that each subtag is a valid private use extension subtag. + * + * This method returns a two element String array. The first element is a string + * containing the concatenation of valid variant subtags which can be appended + * to a BCP-47 tag directly and the second containing the concatenation of + * invalid variant subtags which can be appended to the private use extension + * directly. + * + * This method assumes that {@code variant} contains at least one ill formed + * variant subtag. + */ + private static String[] splitIllformedVariant(String variant) { + final String normalizedVariant = variant.replace('_', '-'); + final String[] subTags = normalizedVariant.split("-"); + + final String[] split = new String[] { "", "" }; + + // First go through the list of variant subtags and check if they're + // valid private use extension subtags. If they're not, we will omit + // the first such subtag and all subtags after. + // + // NOTE: |firstInvalidSubtag| is the index of the first variant + // subtag we decide to omit altogether, whereas |firstIllformedSubtag| is the + // index of the first subtag we decide to append to the private use extension. + // + // In other words: + // [0, firstIllformedSubtag) => expressed as variant subtags. + // [firstIllformedSubtag, firstInvalidSubtag) => expressed as private use + // extension subtags. + // [firstInvalidSubtag, subTags.length) => omitted. + int firstInvalidSubtag = subTags.length; + for (int i = 0; i < subTags.length; ++i) { + if (!isValidBcp47Alphanum(subTags[i], 1, 8)) { + firstInvalidSubtag = i; + break; + } + } + + if (firstInvalidSubtag == 0) { + return split; + } + + // We now consider each subtag that could potentially be appended to + // the private use extension and check if it's valid. + int firstIllformedSubtag = firstInvalidSubtag; + for (int i = 0; i < firstInvalidSubtag; ++i) { + final String subTag = subTags[i]; + // The BCP-47 spec states that : + // - Subtags can be between [5, 8] alphanumeric chars in length. + // - Subtags that start with a number are allowed to be 4 chars in length. + if (subTag.length() >= 5 && subTag.length() <= 8) { + if (!isAsciiAlphaNum(subTag)) { + firstIllformedSubtag = i; + } + } else if (subTag.length() == 4) { + final char firstChar = subTag.charAt(0); + if (!(firstChar >= '0' && firstChar <= '9') || !isAsciiAlphaNum(subTag)) { + firstIllformedSubtag = i; + } + } else { + firstIllformedSubtag = i; + } + } + + split[0] = concatenateRange(subTags, 0, firstIllformedSubtag); + split[1] = concatenateRange(subTags, firstIllformedSubtag, firstInvalidSubtag); + + return split; + } + + /** + * Builds a string by concatenating array elements within the range [start, end). + * The supplied range is assumed to be valid and no checks are performed. + */ + private static String concatenateRange(String[] array, int start, int end) { + StringBuilder builder = new StringBuilder(32); + for (int i = start; i < end; ++i) { + if (i != start) { + builder.append('-'); + } + builder.append(array[i]); + } + + return builder.toString(); + } + + /** + * Returns the set of BCP-47 extensions this locale contains. + * + * See <a href="https://tools.ietf.org/html/bcp47#section-2.1"> + * the IETF BCP-47 specification</a> (Section 2.2.6) for details. + * + * @since 1.7 + */ + public Set<Character> getExtensionKeys() { + return extensions.keySet(); + } + + /** + * Returns the BCP-47 extension whose key is {@code extensionKey}, or {@code null} + * if this locale does not contain the extension. + * + * Individual Keywords and attributes for the unicode + * locale extension can be fetched using {@link #getUnicodeLocaleAttributes()}, + * {@link #getUnicodeLocaleKeys()} and {@link #getUnicodeLocaleType}. + * + * @since 1.7 + */ + public String getExtension(char extensionKey) { + return extensions.get(extensionKey); + } + + /** + * Returns the {@code type} for the specified unicode locale extension {@code key}. + * + * For more information about types and keywords, see {@link Builder#setUnicodeLocaleKeyword} + * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a> + * + * @since 1.7 + */ + public String getUnicodeLocaleType(String keyWord) { + return unicodeKeywords.get(keyWord); + } + + /** + * Returns the set of unicode locale extension attributes this locale contains. + * + * For more information about attributes, see {@link Builder#addUnicodeLocaleAttribute} + * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a> + * + * @since 1.7 + */ + public Set<String> getUnicodeLocaleAttributes() { + return unicodeAttributes; + } + + /** + * Returns the set of unicode locale extension keywords this locale contains. + * + * For more information about types and keywords, see {@link Builder#setUnicodeLocaleKeyword} + * and <a href="http://www.unicode.org/reports/tr35/#BCP47">Unicode Technical Standard #35</a> + * + * @since 1.7 + */ + public Set<String> getUnicodeLocaleKeys() { + return unicodeKeywords.keySet(); + } + @Override public synchronized int hashCode() { - return countryCode.hashCode() + languageCode.hashCode() - + variantCode.hashCode(); + return countryCode.hashCode() + + languageCode.hashCode() + variantCode.hashCode() + + scriptCode.hashCode() + extensions.hashCode(); } /** @@ -592,7 +1599,9 @@ public final class Locale implements Cloneable, Serializable { if (locale == null) { throw new NullPointerException("locale == null"); } + String languageTag = locale.toLanguageTag(); defaultLocale = locale; + ICU.setDefaultLocale(languageTag); } /** @@ -610,30 +1619,59 @@ public final class Locale implements Cloneable, Serializable { public final String toString() { String result = cachedToStringResult; if (result == null) { - result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode); + result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode, + scriptCode, extensions); } return result; } - private static String toNewString(String languageCode, String countryCode, String variantCode) { + private static String toNewString(String languageCode, String countryCode, + String variantCode, String scriptCode, Map<Character, String> extensions) { // The string form of a locale that only has a variant is the empty string. if (languageCode.length() == 0 && countryCode.length() == 0) { return ""; } + // Otherwise, the output format is "ll_cc_variant", where language and country are always // two letters, but the variant is an arbitrary length. A size of 11 characters has room // for "en_US_POSIX", the largest "common" value. (In practice, the string form is almost // always 5 characters: "ll_cc".) StringBuilder result = new StringBuilder(11); result.append(languageCode); - if (countryCode.length() > 0 || variantCode.length() > 0) { + + final boolean hasScriptOrExtensions = !scriptCode.isEmpty() || !extensions.isEmpty(); + + if (!countryCode.isEmpty() || !variantCode.isEmpty() || hasScriptOrExtensions) { result.append('_'); } result.append(countryCode); - if (variantCode.length() > 0) { + if (!variantCode.isEmpty() || hasScriptOrExtensions) { result.append('_'); } result.append(variantCode); + + if (hasScriptOrExtensions) { + if (!variantCode.isEmpty()) { + result.append('_'); + } + + // Note that this is notably different from the BCP-47 spec (for + // backwards compatibility). We are forced to append a "#" before the script tag. + // and also put the script code right at the end. + result.append("#"); + if (!scriptCode.isEmpty() ) { + result.append(scriptCode); + } + + // Note the use of "-" instead of "_" before the extensions. + if (!extensions.isEmpty()) { + if (!scriptCode.isEmpty()) { + result.append('-'); + } + result.append(serializeExtensions(extensions)); + } + } + return result.toString(); } @@ -642,6 +1680,8 @@ public final class Locale implements Cloneable, Serializable { new ObjectStreamField("hashcode", int.class), new ObjectStreamField("language", String.class), new ObjectStreamField("variant", String.class), + new ObjectStreamField("script", String.class), + new ObjectStreamField("extensions", String.class), }; private void writeObject(ObjectOutputStream stream) throws IOException { @@ -650,6 +1690,12 @@ public final class Locale implements Cloneable, Serializable { fields.put("hashcode", -1); fields.put("language", languageCode); fields.put("variant", variantCode); + fields.put("script", scriptCode); + + if (!extensions.isEmpty()) { + fields.put("extensions", serializeExtensions(extensions)); + } + stream.writeFields(); } @@ -658,5 +1704,545 @@ public final class Locale implements Cloneable, Serializable { countryCode = (String) fields.get("country", ""); languageCode = (String) fields.get("language", ""); variantCode = (String) fields.get("variant", ""); + scriptCode = (String) fields.get("script", ""); + + this.unicodeKeywords = Collections.EMPTY_MAP; + this.unicodeAttributes = Collections.EMPTY_SET; + this.extensions = Collections.EMPTY_MAP; + + String extensions = (String) fields.get("extensions", null); + if (extensions != null) { + readExtensions(extensions); + } + } + + private void readExtensions(String extensions) { + Map<Character, String> extensionsMap = new TreeMap<Character, String>(); + parseSerializedExtensions(extensions, extensionsMap); + this.extensions = Collections.unmodifiableMap(extensionsMap); + + if (extensionsMap.containsKey(UNICODE_LOCALE_EXTENSION)) { + String unicodeExtension = extensionsMap.get(UNICODE_LOCALE_EXTENSION); + String[] subTags = unicodeExtension.split("-"); + + Map<String, String> unicodeKeywords = new TreeMap<String, String>(); + Set<String> unicodeAttributes = new TreeSet<String>(); + parseUnicodeExtension(subTags, unicodeKeywords, unicodeAttributes); + + this.unicodeKeywords = Collections.unmodifiableMap(unicodeKeywords); + this.unicodeAttributes = Collections.unmodifiableSet(unicodeAttributes); + } + } + + /** + * The serialized form for extensions is straightforward. It's simply + * of the form key1-value1-key2-value2 where each value might in turn contain + * multiple subtags separated by hyphens. Each key is guaranteed to be a single + * character in length. + * + * This method assumes that {@code extensionsMap} is non-empty. + * + * Visible for testing. + * + * @hide + */ + public static String serializeExtensions(Map<Character, String> extensionsMap) { + Iterator<Map.Entry<Character, String>> entryIterator = extensionsMap.entrySet().iterator(); + StringBuilder sb = new StringBuilder(64); + + while (true) { + final Map.Entry<Character, String> entry = entryIterator.next(); + sb.append(entry.getKey()); + sb.append('-'); + sb.append(entry.getValue()); + + if (entryIterator.hasNext()) { + sb.append('-'); + } else { + break; + } + } + + return sb.toString(); + } + + /** + * Visible for testing. + * + * @hide + */ + public static void parseSerializedExtensions(String extString, Map<Character, String> outputMap) { + // This probably isn't the most efficient approach, but it's the + // most straightforward to code. + // + // Start by splitting the string on "-". We will then keep track of + // where each of the extension keys (single characters) appear in the + // original string and then use those indices to construct substrings + // representing the values. + final String[] subTags = extString.split("-"); + final int[] typeStartIndices = new int[subTags.length / 2]; + + int length = 0; + int count = 0; + for (String subTag : subTags) { + if (subTag.length() > 0) { + // Account for the length of the "-" at the end of each subtag. + length += (subTag.length() + 1); + } + + if (subTag.length() == 1) { + typeStartIndices[count++] = length; + } + } + + for (int i = 0; i < count; ++i) { + final int valueStart = typeStartIndices[i]; + // Since the start Index points to the beginning of the next type + // ....prev-k-next..... + // |_ here + // (idx - 2) is the index of the next key + // (idx - 3) is the (non inclusive) end of the previous type. + final int valueEnd = (i == (count - 1)) ? + extString.length() : (typeStartIndices[i + 1] - 3); + + outputMap.put(extString.charAt(typeStartIndices[i] - 2), + extString.substring(valueStart, valueEnd)); + } + } + + + /** + * A UN M.49 is a 3 digit numeric code. + */ + private static boolean isUnM49AreaCode(String code) { + if (code.length() != 3) { + return false; + } + + for (int i = 0; i < 3; ++i) { + final char character = code.charAt(i); + if (!(character >= '0' && character <= '9')) { + return false; + } + } + + return true; + } + + /* + * Checks whether a given string is an ASCII alphanumeric string. + */ + private static boolean isAsciiAlphaNum(String string) { + for (int i = 0; i < string.length(); i++) { + final char character = string.charAt(i); + if (!(character >= 'a' && character <= 'z' || + character >= 'A' && character <= 'Z' || + character >= '0' && character <= '9')) { + return false; + } + } + + return true; + } + + private static boolean isValidBcp47Alpha(String string, int lowerBound, int upperBound) { + final int length = string.length(); + if (length < lowerBound || length > upperBound) { + return false; + } + + for (int i = 0; i < length; ++i) { + final char character = string.charAt(i); + if (!(character >= 'a' && character <= 'z' || + character >= 'A' && character <= 'Z')) { + return false; + } + } + + return true; + } + + private static boolean isValidBcp47Alphanum(String attributeOrType, + int lowerBound, int upperBound) { + if (attributeOrType.length() < lowerBound || attributeOrType.length() > upperBound) { + return false; + } + + return isAsciiAlphaNum(attributeOrType); + } + + private static String titleCaseAsciiWord(String word) { + try { + byte[] chars = word.toLowerCase(Locale.ROOT).getBytes(StandardCharsets.US_ASCII); + chars[0] = (byte) ((int) chars[0] + 'A' - 'a'); + return new String(chars, StandardCharsets.US_ASCII); + } catch (UnsupportedOperationException uoe) { + throw new AssertionError(uoe); + } + } + + /** + * A type list must contain one or more alphanumeric subtags whose lengths + * are between 3 and 8. + */ + private static boolean isValidTypeList(String lowerCaseTypeList) { + final String[] splitList = lowerCaseTypeList.split("-"); + for (String type : splitList) { + if (!isValidBcp47Alphanum(type, 3, 8)) { + return false; + } + } + + return true; + } + + private static void addUnicodeExtensionToExtensionsMap( + Set<String> attributes, Map<String, String> keywords, + Map<Character, String> extensions) { + if (attributes.isEmpty() && keywords.isEmpty()) { + return; + } + + // Assume that the common case is a low number of keywords & attributes + // (usually one or two). + final StringBuilder sb = new StringBuilder(32); + + // All attributes must appear before keywords, in lexical order. + if (!attributes.isEmpty()) { + Iterator<String> attributesIterator = attributes.iterator(); + while (true) { + sb.append(attributesIterator.next()); + if (attributesIterator.hasNext()) { + sb.append('-'); + } else { + break; + } + } + } + + if (!keywords.isEmpty()) { + if (!attributes.isEmpty()) { + sb.append('-'); + } + + Iterator<Map.Entry<String, String>> keywordsIterator = keywords.entrySet().iterator(); + while (true) { + final Map.Entry<String, String> keyWord = keywordsIterator.next(); + sb.append(keyWord.getKey()); + if (!keyWord.getValue().isEmpty()) { + sb.append('-'); + sb.append(keyWord.getValue()); + } + if (keywordsIterator.hasNext()) { + sb.append('-'); + } else { + break; + } + } + } + + extensions.put(UNICODE_LOCALE_EXTENSION, sb.toString()); + } + + /** + * This extension is described by http://www.unicode.org/reports/tr35/#RFC5234 + * unicode_locale_extensions = sep "u" (1*(sep keyword) / 1*(sep attribute) *(sep keyword)). + * + * It must contain at least one keyword or attribute and attributes (if any) + * must appear before keywords. Attributes can't appear after keywords because + * they will be indistinguishable from a subtag of the keyword type. + * + * Visible for testing. + * + * @hide + */ + public static void parseUnicodeExtension(String[] subtags, + Map<String, String> keywords, Set<String> attributes) { + String lastKeyword = null; + List<String> subtagsForKeyword = new ArrayList<String>(); + for (String subtag : subtags) { + if (subtag.length() == 2) { + if (subtagsForKeyword.size() > 0) { + keywords.put(lastKeyword, joinBcp47Subtags(subtagsForKeyword)); + subtagsForKeyword.clear(); + } + + lastKeyword = subtag; + } else if (subtag.length() > 2) { + if (lastKeyword == null) { + attributes.add(subtag); + } else { + subtagsForKeyword.add(subtag); + } + } + } + + if (subtagsForKeyword.size() > 0) { + keywords.put(lastKeyword, joinBcp47Subtags(subtagsForKeyword)); + } else if (lastKeyword != null) { + keywords.put(lastKeyword, ""); + } + } + + /** + * Joins a list of subtags into a BCP-47 tag using the standard separator + * ("-"). + */ + private static String joinBcp47Subtags(List<String> strings) { + final int size = strings.size(); + + StringBuilder sb = new StringBuilder(strings.get(0).length()); + for (int i = 0; i < size; ++i) { + sb.append(strings.get(i)); + if (i != size - 1) { + sb.append('-'); + } + } + + return sb.toString(); + } + + /** + * @hide for internal use only. + */ + public static String adjustLanguageCode(String languageCode) { + String adjusted = languageCode.toLowerCase(Locale.US); + // Map new language codes to the obsolete language + // codes so the correct resource bundles will be used. + if (languageCode.equals("he")) { + adjusted = "iw"; + } else if (languageCode.equals("id")) { + adjusted = "in"; + } else if (languageCode.equals("yi")) { + adjusted = "ji"; + } + + 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; + } + + /** + * Scans elements of {@code subtags} in the range {@code [startIndex, endIndex)} + * and appends valid variant subtags upto the first invalid subtag (if any) to + * {@code normalizedVariants}. + */ + private static void extractVariantSubtags(String[] subtags, int startIndex, int endIndex, + List<String> normalizedVariants) { + for (int i = startIndex; i < endIndex; i++) { + final String subtag = subtags[i]; + + if (Builder.isValidVariantSubtag(subtag)) { + normalizedVariants.add(subtag); + } else { + break; + } + } + } + + /** + * Scans elements of {@code subtags} in the range {@code [startIndex, endIndex)} + * and inserts valid extensions into {@code extensions}. The scan is aborted + * when an invalid extension is encountered. Returns the index of the first + * unparsable element of {@code subtags}. + */ + private static int extractExtensions(String[] subtags, int startIndex, int endIndex, + Map<Character, String> extensions) { + int privateUseExtensionIndex = -1; + int extensionKeyIndex = -1; + + int i = startIndex; + for (; i < endIndex; i++) { + final String subtag = subtags[i]; + + final boolean parsingPrivateUse = (privateUseExtensionIndex != -1) && + (extensionKeyIndex == privateUseExtensionIndex); + + // Note that private use extensions allow subtags of length 1. + // Private use extensions *must* come last, so there's no ambiguity + // in that case. + if (subtag.length() == 1 && !parsingPrivateUse) { + // Emit the last extension we encountered if any. First check + // whether we encountered two keys in a row (which is an error). + // Also checks if we already have an extension with the same key, + // which is again an error. + if (extensionKeyIndex != -1) { + if ((i - 1) == extensionKeyIndex) { + return extensionKeyIndex; + } + + final String key = subtags[extensionKeyIndex]; + if (extensions.containsKey(key.charAt(0))) { + return extensionKeyIndex; + } + + final String value = concatenateRange(subtags, extensionKeyIndex + 1, i); + extensions.put(key.charAt(0), value.toLowerCase(Locale.ROOT)); + } + + // Mark the start of the next extension. Also keep track of whether this + // is a private use extension, and throw an error if it doesn't come last. + extensionKeyIndex = i; + if ("x".equals(subtag)) { + privateUseExtensionIndex = i; + } else if (privateUseExtensionIndex != -1) { + // The private use extension must come last. + return privateUseExtensionIndex; + } + } else if (extensionKeyIndex != -1) { + // We must have encountered a valid key in order to start parsing + // its subtags. + if (!isValidBcp47Alphanum(subtag, parsingPrivateUse ? 1 : 2, 8)) { + return i; + } + } else { + // Encountered a value without a preceding key. + return i; + } + } + + if (extensionKeyIndex != -1) { + if ((i - 1) == extensionKeyIndex) { + return extensionKeyIndex; + } + + final String key = subtags[extensionKeyIndex]; + if (extensions.containsKey(key.charAt(0))) { + return extensionKeyIndex; + } + + final String value = concatenateRange(subtags, extensionKeyIndex + 1, i); + extensions.put(key.charAt(0), value.toLowerCase(Locale.ROOT)); + } + + return i; + } + + private static Locale forLanguageTag(/* @Nonnull */ String tag, boolean strict) { + final String converted = convertGrandfatheredTag(tag); + final String[] subtags = converted.split("-"); + + int lastSubtag = subtags.length; + for (int i = 0; i < subtags.length; ++i) { + final String subtag = subtags[i]; + if (subtag.isEmpty() || subtag.length() > 8) { + if (strict) { + throw new IllformedLocaleException("Invalid subtag at index: " + i + + " in tag: " + tag); + } else { + lastSubtag = (i - 1); + } + + break; + } + } + + final String languageCode = Builder.normalizeAndValidateLanguage(subtags[0], strict); + String scriptCode = ""; + int nextSubtag = 1; + if (lastSubtag > nextSubtag) { + scriptCode = Builder.normalizeAndValidateScript(subtags[nextSubtag], false /* strict */); + if (!scriptCode.isEmpty()) { + nextSubtag++; + } + } + + String regionCode = ""; + if (lastSubtag > nextSubtag) { + regionCode = Builder.normalizeAndValidateRegion(subtags[nextSubtag], false /* strict */); + if (!regionCode.isEmpty()) { + nextSubtag++; + } + } + + List<String> variants = null; + if (lastSubtag > nextSubtag) { + variants = new ArrayList<String>(); + extractVariantSubtags(subtags, nextSubtag, lastSubtag, variants); + nextSubtag += variants.size(); + } + + Map<Character, String> extensions = Collections.EMPTY_MAP; + if (lastSubtag > nextSubtag) { + extensions = new TreeMap<Character, String>(); + nextSubtag = extractExtensions(subtags, nextSubtag, lastSubtag, extensions); + } + + if (nextSubtag != lastSubtag) { + if (strict) { + throw new IllformedLocaleException("Unparseable subtag: " + subtags[nextSubtag] + + " from language tag: " + tag); + } + } + + Set<String> unicodeKeywords = Collections.EMPTY_SET; + Map<String, String> unicodeAttributes = Collections.EMPTY_MAP; + if (extensions.containsKey(UNICODE_LOCALE_EXTENSION)) { + unicodeKeywords = new TreeSet<String>(); + unicodeAttributes = new TreeMap<String, String>(); + parseUnicodeExtension(extensions.get(UNICODE_LOCALE_EXTENSION).split("-"), + unicodeAttributes, unicodeKeywords); + } + + String variantCode = ""; + if (variants != null && !variants.isEmpty()) { + StringBuilder variantsBuilder = new StringBuilder(variants.size() * 8); + for (int i = 0; i < variants.size(); ++i) { + if (i != 0) { + variantsBuilder.append('_'); + } + variantsBuilder.append(variants.get(i)); + } + variantCode = variantsBuilder.toString(); + } + + return new Locale(languageCode, regionCode, variantCode, scriptCode, + unicodeKeywords, unicodeAttributes, extensions, true /* has validated fields */); } } diff --git a/luni/src/main/java/java/util/Properties.java b/luni/src/main/java/java/util/Properties.java index cd19295..532d35c 100644 --- a/luni/src/main/java/java/util/Properties.java +++ b/luni/src/main/java/java/util/Properties.java @@ -52,7 +52,7 @@ import org.xml.sax.SAXParseException; * values to be used when a given key is not found in this {@code Properties} * instance. * - * <a name="character_encoding"><h3>Character Encoding</h3></a> + * <a name="character_encoding"></a><h3>Character Encoding</h3> * <p>Note that in some cases {@code Properties} uses ISO-8859-1 instead of UTF-8. * ISO-8859-1 is only capable of representing a tiny subset of Unicode. * Use either the {@code loadFromXML}/{@code storeToXML} methods (which use UTF-8 by diff --git a/luni/src/main/java/java/util/Random.java b/luni/src/main/java/java/util/Random.java index 4a67244..091d584 100644 --- a/luni/src/main/java/java/util/Random.java +++ b/luni/src/main/java/java/util/Random.java @@ -56,15 +56,19 @@ public class Random implements Serializable { private double nextNextGaussian; /** + * Used to generate initial seeds. + */ + private static volatile long seedBase = 0; + + /** * Constructs a random generator with an initial state that is * unlikely to be duplicated by a subsequent instantiation. - * - * <p>The initial state (that is, the seed) is <i>partially</i> based - * on the current time of day in milliseconds. */ public Random() { - // Note: Using identityHashCode() to be hermetic wrt subclasses. - setSeed(System.currentTimeMillis() + System.identityHashCode(this)); + // Note: Don't use identityHashCode(this) since that causes the monitor to + // get inflated when we synchronize. + setSeed(System.nanoTime() + seedBase); + ++seedBase; } /** @@ -85,6 +89,9 @@ public class Random implements Serializable { * Volume 2: Seminumerical Algorithms</i>, section 3.2.1. * * <p>Most applications will want to use one of this class' convenience methods instead. + * + * <p>Subclasses only need to override this method to alter the behavior + * of all the public methods. */ protected synchronized int next(int bits) { seed = (seed * multiplier + 0xbL) & ((1L << 48) - 1); diff --git a/luni/src/main/java/java/util/Scanner.java b/luni/src/main/java/java/util/Scanner.java index 7d504b7..7d0e795 100644 --- a/luni/src/main/java/java/util/Scanner.java +++ b/luni/src/main/java/java/util/Scanner.java @@ -159,12 +159,15 @@ public final class Scanner implements Closeable, Iterator<String> { if (charsetName == null) { throw new IllegalArgumentException("charsetName == null"); } + + InputStreamReader streamReader; try { - setInput(new InputStreamReader(fis, charsetName)); + streamReader = new InputStreamReader(fis, charsetName); } catch (UnsupportedEncodingException e) { IoUtils.closeQuietly(fis); throw new IllegalArgumentException(e.getMessage()); } + initialize(streamReader); } /** @@ -174,7 +177,7 @@ public final class Scanner implements Closeable, Iterator<String> { * the string to be scanned. */ public Scanner(String src) { - setInput(new StringReader(src)); + initialize(new StringReader(src)); } /** @@ -203,11 +206,14 @@ public final class Scanner implements Closeable, Iterator<String> { if (src == null) { throw new NullPointerException("src == null"); } + + InputStreamReader streamReader; try { - setInput(new InputStreamReader(src, charsetName)); + streamReader = new InputStreamReader(src, charsetName); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException(e.getMessage()); } + initialize(streamReader); } /** @@ -220,7 +226,7 @@ public final class Scanner implements Closeable, Iterator<String> { if (src == null) { throw new NullPointerException("src == null"); } - setInput(src); + initialize(src); } /** @@ -252,13 +258,14 @@ public final class Scanner implements Closeable, Iterator<String> { if (charsetName == null) { throw new IllegalArgumentException("charsetName == null"); } - setInput(Channels.newReader(src, charsetName)); + initialize(Channels.newReader(src, charsetName)); } - private void setInput(Readable input) { + private void initialize(Readable input) { this.input = input; - buffer.limit(0); - matcher = delimiter.matcher(buffer); + matcher = delimiter.matcher(""); + matcher.useTransparentBounds(true); + matcher.useAnchoringBounds(false); } /** @@ -535,7 +542,7 @@ public final class Scanner implements Closeable, Iterator<String> { checkOpen(); checkNotNull(pattern); matchSuccessful = false; - saveCurrentStatus(); + prepareForScan(); // if the next token exists, set the match region, otherwise return // false if (!setTokenRegion()) { @@ -790,7 +797,7 @@ public final class Scanner implements Closeable, Iterator<String> { * @throws IllegalStateException if this {@code Scanner} is closed. */ public boolean hasNextLine() { - saveCurrentStatus(); + prepareForScan(); String result = findWithinHorizon(LINE_PATTERN, 0); recoverPreviousStatus(); return result != null; @@ -954,7 +961,7 @@ public final class Scanner implements Closeable, Iterator<String> { checkOpen(); checkNotNull(pattern); matchSuccessful = false; - saveCurrentStatus(); + prepareForScan(); if (!setTokenRegion()) { recoverPreviousStatus(); // if setting match region fails @@ -1204,7 +1211,7 @@ public final class Scanner implements Closeable, Iterator<String> { Pattern floatPattern = getFloatPattern(); String floatString = next(floatPattern); floatString = removeLocaleInfoFromFloat(floatString); - double doubleValue = 0; + double doubleValue; try { doubleValue = Double.parseDouble(floatString); } catch (NumberFormatException e) { @@ -1248,7 +1255,7 @@ public final class Scanner implements Closeable, Iterator<String> { Pattern floatPattern = getFloatPattern(); String floatString = next(floatPattern); floatString = removeLocaleInfoFromFloat(floatString); - float floatValue = 0; + float floatValue; try { floatValue = Float.parseFloat(floatString); } catch (NumberFormatException e) { @@ -1310,7 +1317,7 @@ public final class Scanner implements Closeable, Iterator<String> { Pattern integerPattern = getIntegerPattern(radix); String intString = next(integerPattern); intString = removeLocaleInfo(intString, int.class); - int intValue = 0; + int intValue; try { intValue = Integer.parseInt(intString, radix); } catch (NumberFormatException e) { @@ -1340,7 +1347,7 @@ public final class Scanner implements Closeable, Iterator<String> { matcher.usePattern(LINE_PATTERN); matcher.region(findStartIndex, bufferLength); - String result = null; + String result; while (true) { if (matcher.find()) { if (inputExhausted || matcher.end() != bufferLength @@ -1422,7 +1429,7 @@ public final class Scanner implements Closeable, Iterator<String> { Pattern integerPattern = getIntegerPattern(radix); String intString = next(integerPattern); intString = removeLocaleInfo(intString, int.class); - long longValue = 0; + long longValue; try { longValue = Long.parseLong(intString, radix); } catch (NumberFormatException e) { @@ -1484,7 +1491,7 @@ public final class Scanner implements Closeable, Iterator<String> { Pattern integerPattern = getIntegerPattern(radix); String intString = next(integerPattern); intString = removeLocaleInfo(intString, int.class); - short shortValue = 0; + short shortValue; try { shortValue = Short.parseShort(intString, radix); } catch (NumberFormatException e) { @@ -1662,23 +1669,46 @@ public final class Scanner implements Closeable, Iterator<String> { } /* - * Change the matcher's string after reading input + * Change the matcher's input after modifying the contents of the buffer. + * The current implementation of Matcher causes a copy of the buffer to be taken. */ private void resetMatcher() { - if (matcher == null) { - matcher = delimiter.matcher(buffer); - } else { - matcher.reset(buffer); - } - matcher.useTransparentBounds(true); - matcher.useAnchoringBounds(false); + matcher.reset(buffer); matcher.region(findStartIndex, bufferLength); } /* - * Save the matcher's last find position - */ - private void saveCurrentStatus() { + * Recover buffer space for characters that are already processed and save the matcher's state + * in case parsing fails. See recoverPrevousState. This method must be called before + * any buffer offsets are calculated. + */ + private void prepareForScan() { + // Compacting the buffer recovers space taken by already processed characters. This does not + // prevent the buffer growing in all situations but keeps the buffer small when delimiters + // exist regularly. + if (findStartIndex >= buffer.capacity() / 2) { + // When over half the buffer is filled with characters no longer being considered by the + // scanner we take the cost of compacting the buffer. + + // Move all characters from [findStartIndex, findStartIndex + remaining()) to + // [0, remaining()). + int oldPosition = buffer.position(); + buffer.position(findStartIndex); + buffer.compact(); + buffer.position(oldPosition); + + // Update Scanner state to reflect the new buffer state. + bufferLength -= findStartIndex; + findStartIndex = 0; + preStartIndex = -1; + + // The matcher must also be informed that the buffer has changed because it operates on + // a String copy. + resetMatcher(); + } + + // Save the matcher's last find position so it can be returned to if the next token cannot + // be parsed. preStartIndex = findStartIndex; } @@ -1822,7 +1852,7 @@ public final class Scanner implements Closeable, Iterator<String> { boolean negative = removeLocaleSign(tokenBuilder); // Remove group separator String groupSeparator = String.valueOf(dfs.getGroupingSeparator()); - int separatorIndex = -1; + int separatorIndex; while ((separatorIndex = tokenBuilder.indexOf(groupSeparator)) != -1) { tokenBuilder.delete(separatorIndex, separatorIndex + 1); } @@ -1909,9 +1939,9 @@ public final class Scanner implements Closeable, Iterator<String> { */ private boolean setTokenRegion() { // The position where token begins - int tokenStartIndex = 0; + int tokenStartIndex; // The position where token ends - int tokenEndIndex = 0; + int tokenEndIndex; // Use delimiter pattern matcher.usePattern(delimiter); matcher.region(findStartIndex, bufferLength); @@ -1945,8 +1975,7 @@ public final class Scanner implements Closeable, Iterator<String> { if (matcher.find()) { findComplete = true; // If just delimiter remains - if (matcher.start() == findStartIndex - && matcher.end() == bufferLength) { + if (matcher.start() == findStartIndex && matcher.end() == bufferLength) { // If more input resource exists if (!inputExhausted) { readMore(); @@ -1964,7 +1993,7 @@ public final class Scanner implements Closeable, Iterator<String> { } } tokenStartIndex = matcher.end(); - findStartIndex = matcher.end(); + findStartIndex = tokenStartIndex; return tokenStartIndex; } @@ -1984,7 +2013,7 @@ public final class Scanner implements Closeable, Iterator<String> { setSuccess = true; } // If the first delimiter of scanner is not at the find start position - if (-1 != findIndex && preStartIndex != matcher.start()) { + if (findIndex != -1 && preStartIndex != matcher.start()) { tokenStartIndex = preStartIndex; tokenEndIndex = matcher.start(); findStartIndex = matcher.start(); @@ -1996,7 +2025,7 @@ public final class Scanner implements Closeable, Iterator<String> { } private int findDelimiterAfter() { - int tokenEndIndex = 0; + int tokenEndIndex; boolean findComplete = false; while (!findComplete) { if (matcher.find()) { @@ -2014,7 +2043,7 @@ public final class Scanner implements Closeable, Iterator<String> { } } tokenEndIndex = matcher.start(); - findStartIndex = matcher.start(); + findStartIndex = tokenEndIndex; return tokenEndIndex; } @@ -2032,7 +2061,7 @@ public final class Scanner implements Closeable, Iterator<String> { } // Read input resource - int readCount = 0; + int readCount; try { buffer.limit(buffer.capacity()); buffer.position(oldBufferLength); diff --git a/luni/src/main/java/java/util/TimeZone.java b/luni/src/main/java/java/util/TimeZone.java index c024e8d..854a4a6 100644 --- a/luni/src/main/java/java/util/TimeZone.java +++ b/luni/src/main/java/java/util/TimeZone.java @@ -63,7 +63,7 @@ import org.apache.harmony.luni.internal.util.TimezoneGetter; * * @see Calendar * @see GregorianCalendar - * @see SimpleDateFormat + * @see java.text.SimpleDateFormat */ public abstract class TimeZone implements Serializable, Cloneable { private static final long serialVersionUID = 3581463369166924961L; @@ -206,27 +206,48 @@ public abstract class TimeZone implements Serializable, Cloneable { // upgrade to icu4c 50 and rewrite the underlying native code. See also the // "element[j] != null" check in SimpleDateFormat.parseTimeZone, and the extra work in // DateFormatSymbols.getZoneStrings. - - int offset = getRawOffset(); + int offsetMillis = getRawOffset(); if (daylightTime) { - offset += getDSTSavings(); + offsetMillis += getDSTSavings(); } - offset /= 60000; + return createGmtOffsetString(true /* includeGmt */, true /* includeMinuteSeparator */, + offsetMillis); + } + + /** + * Returns a string representation of an offset from UTC. + * + * <p>The format is "[GMT](+|-)HH[:]MM". The output is not localized. + * + * @param includeGmt true to include "GMT", false to exclude + * @param includeMinuteSeparator true to include the separator between hours and minutes, false + * to exclude. + * @param offsetMillis the offset from UTC + * + * @hide used internally by SimpleDateFormat + */ + public static String createGmtOffsetString(boolean includeGmt, + boolean includeMinuteSeparator, int offsetMillis) { + int offsetMinutes = offsetMillis / 60000; char sign = '+'; - if (offset < 0) { + if (offsetMinutes < 0) { sign = '-'; - offset = -offset; + offsetMinutes = -offsetMinutes; } StringBuilder builder = new StringBuilder(9); - builder.append("GMT"); + if (includeGmt) { + builder.append("GMT"); + } builder.append(sign); - appendNumber(builder, 2, offset / 60); - builder.append(':'); - appendNumber(builder, 2, offset % 60); + appendNumber(builder, 2, offsetMinutes / 60); + if (includeMinuteSeparator) { + builder.append(':'); + } + appendNumber(builder, 2, offsetMinutes % 60); return builder.toString(); } - private void appendNumber(StringBuilder builder, int count, int value) { + private static void appendNumber(StringBuilder builder, int count, int value) { String string = Integer.toString(value); for (int i = 0; i < count - string.length(); i++) { builder.append('0'); @@ -329,7 +350,6 @@ public abstract class TimeZone implements Serializable, Cloneable { } // Special cases? These can clone an existing instance. - // TODO: should we just add a cache to ZoneInfoDB instead? if (id.length() == 3) { if (id.equals("GMT")) { return (TimeZone) GMT.clone(); diff --git a/luni/src/main/java/java/util/Timer.java b/luni/src/main/java/java/util/Timer.java index 25ac432..7192f9b 100644 --- a/luni/src/main/java/java/util/Timer.java +++ b/luni/src/main/java/java/util/Timer.java @@ -356,9 +356,7 @@ public class Timer { * Creates a new named {@code Timer} which may be specified to be run as a * daemon thread. * - * @param name the name of the {@code Timer}. - * @param isDaemon true if {@code Timer}'s thread should be a daemon thread. - * @throws NullPointerException is {@code name} is {@code null} + * @throws NullPointerException if {@code name == null} */ public Timer(String name, boolean isDaemon) { if (name == null) { @@ -371,8 +369,7 @@ public class Timer { /** * Creates a new named {@code Timer} which does not run as a daemon thread. * - * @param name the name of the Timer. - * @throws NullPointerException is {@code name} is {@code null} + * @throws NullPointerException if {@code name == null} */ public Timer(String name) { this(name, false); diff --git a/luni/src/main/java/java/util/TreeSet.java b/luni/src/main/java/java/util/TreeSet.java index 502329e..791ffa6 100644 --- a/luni/src/main/java/java/util/TreeSet.java +++ b/luni/src/main/java/java/util/TreeSet.java @@ -258,7 +258,7 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, /** * Returns the first element in this set. - * @exception NoSuchElementException when this TreeSet is empty + * @throws NoSuchElementException when this TreeSet is empty */ public E first() { return backingMap.firstKey(); @@ -266,7 +266,7 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, /** * Returns the last element in this set. - * @exception NoSuchElementException when this TreeSet is empty + * @throws NoSuchElementException when this TreeSet is empty */ public E last() { return backingMap.lastKey(); @@ -413,10 +413,10 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, * @return a subset where the elements are greater or equal to * <code>start</code> and less than <code>end</code> * - * @exception ClassCastException + * @throws ClassCastException * when the start or end object cannot be compared with the * elements in this TreeSet - * @exception NullPointerException + * @throws NullPointerException * when the start or end object is null and the comparator * cannot handle null */ @@ -434,10 +434,10 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, * the end element * @return a subset where the elements are less than <code>end</code> * - * @exception ClassCastException + * @throws ClassCastException * when the end object cannot be compared with the elements * in this TreeSet - * @exception NullPointerException + * @throws NullPointerException * when the end object is null and the comparator cannot * handle null */ @@ -457,10 +457,10 @@ public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, * @return a subset where the elements are greater or equal to * <code>start</code> * - * @exception ClassCastException + * @throws ClassCastException * when the start object cannot be compared with the elements * in this TreeSet - * @exception NullPointerException + * @throws NullPointerException * when the start object is null and the comparator cannot * handle null */ diff --git a/luni/src/main/java/java/util/UUID.java b/luni/src/main/java/java/util/UUID.java index 3594d87..020ac95 100644 --- a/luni/src/main/java/java/util/UUID.java +++ b/luni/src/main/java/java/util/UUID.java @@ -182,28 +182,17 @@ public final class UUID implements Serializable, Comparable<UUID> { throw new NullPointerException("uuid == null"); } - int[] position = new int[5]; - int lastPosition = 1; - int startPosition = 0; - - int i = 0; - for (; i < position.length && lastPosition > 0; i++) { - position[i] = uuid.indexOf("-", startPosition); - lastPosition = position[i]; - startPosition = position[i] + 1; - } - - // should have and only can have four "-" in UUID - if (i != position.length || lastPosition != -1) { + String[] parts = uuid.split("-"); + if (parts.length != 5) { throw new IllegalArgumentException("Invalid UUID: " + uuid); } - long m1 = Long.parseLong(uuid.substring(0, position[0]), 16); - long m2 = Long.parseLong(uuid.substring(position[0] + 1, position[1]), 16); - long m3 = Long.parseLong(uuid.substring(position[1] + 1, position[2]), 16); + long m1 = Long.parsePositiveLong(parts[0], 16); + long m2 = Long.parsePositiveLong(parts[1], 16); + long m3 = Long.parsePositiveLong(parts[2], 16); - long lsb1 = Long.parseLong(uuid.substring(position[2] + 1, position[3]), 16); - long lsb2 = Long.parseLong(uuid.substring(position[3] + 1), 16); + long lsb1 = Long.parsePositiveLong(parts[3], 16); + long lsb2 = Long.parsePositiveLong(parts[4], 16); long msb = (m1 << 32) | (m2 << 16) | m3; long lsb = (lsb1 << 48) | lsb2; diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java index c85a5cc..ea3b1e9 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentHashMap.java @@ -5,865 +5,729 @@ */ package java.util.concurrent; -import java.util.concurrent.locks.*; -import java.util.*; + +import java.io.ObjectStreamField; import java.io.Serializable; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; // BEGIN android-note // removed link to collections framework docs +// removed links to hidden api // END android-note /** * A hash table supporting full concurrency of retrievals and - * adjustable expected concurrency for updates. This class obeys the + * high expected concurrency for updates. This class obeys the * same functional specification as {@link java.util.Hashtable}, and * includes versions of methods corresponding to each method of - * <tt>Hashtable</tt>. However, even though all operations are + * {@code Hashtable}. However, even though all operations are * thread-safe, retrieval operations do <em>not</em> entail locking, * and there is <em>not</em> any support for locking the entire table * in a way that prevents all access. This class is fully - * interoperable with <tt>Hashtable</tt> in programs that rely on its + * interoperable with {@code Hashtable} in programs that rely on its * thread safety but not on its synchronization details. * - * <p> Retrieval operations (including <tt>get</tt>) generally do not - * block, so may overlap with update operations (including - * <tt>put</tt> and <tt>remove</tt>). Retrievals reflect the results - * of the most recently <em>completed</em> update operations holding - * upon their onset. For aggregate operations such as <tt>putAll</tt> - * and <tt>clear</tt>, concurrent retrievals may reflect insertion or - * removal of only some entries. Similarly, Iterators and - * Enumerations return elements reflecting the state of the hash table - * at some point at or since the creation of the iterator/enumeration. - * They do <em>not</em> throw {@link ConcurrentModificationException}. - * However, iterators are designed to be used by only one thread at a time. + * <p>Retrieval operations (including {@code get}) generally do not + * block, so may overlap with update operations (including {@code put} + * and {@code remove}). Retrievals reflect the results of the most + * recently <em>completed</em> update operations holding upon their + * onset. (More formally, an update operation for a given key bears a + * <em>happens-before</em> relation with any (non-null) retrieval for + * that key reporting the updated value.) For aggregate operations + * such as {@code putAll} and {@code clear}, concurrent retrievals may + * reflect insertion or removal of only some entries. Similarly, + * Iterators and Enumerations return elements reflecting the state of + * the hash table at some point at or since the creation of the + * iterator/enumeration. They do <em>not</em> throw {@link + * ConcurrentModificationException}. However, iterators are designed + * to be used by only one thread at a time. Bear in mind that the + * results of aggregate status methods including {@code size}, {@code + * isEmpty}, and {@code containsValue} are typically useful only when + * a map is not undergoing concurrent updates in other threads. + * Otherwise the results of these methods reflect transient states + * that may be adequate for monitoring or estimation purposes, but not + * for program control. * - * <p> The allowed concurrency among update operations is guided by - * the optional <tt>concurrencyLevel</tt> constructor argument - * (default <tt>16</tt>), which is used as a hint for internal sizing. The - * table is internally partitioned to try to permit the indicated - * number of concurrent updates without contention. Because placement - * in hash tables is essentially random, the actual concurrency will - * vary. Ideally, you should choose a value to accommodate as many - * threads as will ever concurrently modify the table. Using a - * significantly higher value than you need can waste space and time, - * and a significantly lower value can lead to thread contention. But - * overestimates and underestimates within an order of magnitude do - * not usually have much noticeable impact. A value of one is - * appropriate when it is known that only one thread will modify and - * all others will only read. Also, resizing this or any other kind of - * hash table is a relatively slow operation, so, when possible, it is - * a good idea to provide estimates of expected table sizes in - * constructors. + * <p>The table is dynamically expanded when there are too many + * collisions (i.e., keys that have distinct hash codes but fall into + * the same slot modulo the table size), with the expected average + * effect of maintaining roughly two bins per mapping (corresponding + * to a 0.75 load factor threshold for resizing). There may be much + * variance around this average as mappings are added and removed, but + * overall, this maintains a commonly accepted time/space tradeoff for + * hash tables. However, resizing this or any other kind of hash + * table may be a relatively slow operation. When possible, it is a + * good idea to provide a size estimate as an optional {@code + * initialCapacity} constructor argument. An additional optional + * {@code loadFactor} constructor argument provides a further means of + * customizing initial table capacity by specifying the table density + * to be used in calculating the amount of space to allocate for the + * given number of elements. Also, for compatibility with previous + * versions of this class, constructors may optionally specify an + * expected {@code concurrencyLevel} as an additional hint for + * internal sizing. Note that using many keys with exactly the same + * {@code hashCode()} is a sure way to slow down performance of any + * hash table. To ameliorate impact, when keys are {@link Comparable}, + * this class may use comparison order among keys to help break ties. * * <p>This class and its views and iterators implement all of the * <em>optional</em> methods of the {@link Map} and {@link Iterator} * interfaces. * - * <p> Like {@link Hashtable} but unlike {@link HashMap}, this class - * does <em>not</em> allow <tt>null</tt> to be used as a key or value. + * <p>Like {@link Hashtable} but unlike {@link HashMap}, this class + * does <em>not</em> allow {@code null} to be used as a key or value. * * @since 1.5 * @author Doug Lea * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values */ -public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> - implements ConcurrentMap<K, V>, Serializable { +public class ConcurrentHashMap<K,V> extends java.util.AbstractMap<K,V> + implements ConcurrentMap<K,V>, Serializable { private static final long serialVersionUID = 7249069246763182397L; /* - * The basic strategy is to subdivide the table among Segments, - * each of which itself is a concurrently readable hash table. To - * reduce footprint, all but one segments are constructed only - * when first needed (see ensureSegment). To maintain visibility - * in the presence of lazy construction, accesses to segments as - * well as elements of segment's table must use volatile access, - * which is done via Unsafe within methods segmentAt etc - * below. These provide the functionality of AtomicReferenceArrays - * but reduce the levels of indirection. Additionally, - * volatile-writes of table elements and entry "next" fields - * within locked operations use the cheaper "lazySet" forms of - * writes (via putOrderedObject) because these writes are always - * followed by lock releases that maintain sequential consistency - * of table updates. - * - * Historical note: The previous version of this class relied - * heavily on "final" fields, which avoided some volatile reads at - * the expense of a large initial footprint. Some remnants of - * that design (including forced construction of segment 0) exist - * to ensure serialization compatibility. + * Overview: + * + * The primary design goal of this hash table is to maintain + * concurrent readability (typically method get(), but also + * iterators and related methods) while minimizing update + * contention. Secondary goals are to keep space consumption about + * the same or better than java.util.HashMap, and to support high + * initial insertion rates on an empty table by many threads. + * + * This map usually acts as a binned (bucketed) hash table. Each + * key-value mapping is held in a Node. Most nodes are instances + * of the basic Node class with hash, key, value, and next + * fields. However, various subclasses exist: TreeNodes are + * arranged in balanced trees, not lists. TreeBins hold the roots + * of sets of TreeNodes. ForwardingNodes are placed at the heads + * of bins during resizing. ReservationNodes are used as + * placeholders while establishing values in computeIfAbsent and + * related methods. The types TreeBin, ForwardingNode, and + * ReservationNode do not hold normal user keys, values, or + * hashes, and are readily distinguishable during search etc + * because they have negative hash fields and null key and value + * fields. (These special nodes are either uncommon or transient, + * so the impact of carrying around some unused fields is + * insignificant.) + * + * The table is lazily initialized to a power-of-two size upon the + * first insertion. Each bin in the table normally contains a + * list of Nodes (most often, the list has only zero or one Node). + * Table accesses require volatile/atomic reads, writes, and + * CASes. Because there is no other way to arrange this without + * adding further indirections, we use intrinsics + * (sun.misc.Unsafe) operations. + * + * We use the top (sign) bit of Node hash fields for control + * purposes -- it is available anyway because of addressing + * constraints. Nodes with negative hash fields are specially + * handled or ignored in map methods. + * + * Insertion (via put or its variants) of the first node in an + * empty bin is performed by just CASing it to the bin. This is + * by far the most common case for put operations under most + * key/hash distributions. Other update operations (insert, + * delete, and replace) require locks. We do not want to waste + * the space required to associate a distinct lock object with + * each bin, so instead use the first node of a bin list itself as + * a lock. Locking support for these locks relies on builtin + * "synchronized" monitors. + * + * Using the first node of a list as a lock does not by itself + * suffice though: When a node is locked, any update must first + * validate that it is still the first node after locking it, and + * retry if not. Because new nodes are always appended to lists, + * once a node is first in a bin, it remains first until deleted + * or the bin becomes invalidated (upon resizing). + * + * The main disadvantage of per-bin locks is that other update + * operations on other nodes in a bin list protected by the same + * lock can stall, for example when user equals() or mapping + * functions take a long time. However, statistically, under + * random hash codes, this is not a common problem. Ideally, the + * frequency of nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average, given the resizing threshold + * of 0.75, although with a large variance because of resizing + * granularity. Ignoring variance, the expected occurrences of + * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The + * first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million + * + * Lock contention probability for two threads accessing distinct + * elements is roughly 1 / (8 * #elements) under random hashes. + * + * Actual hash code distributions encountered in practice + * sometimes deviate significantly from uniform randomness. This + * includes the case when N > (1<<30), so some keys MUST collide. + * Similarly for dumb or hostile usages in which multiple keys are + * designed to have identical hash codes or ones that differs only + * in masked-out high bits. So we use a secondary strategy that + * applies when the number of nodes in a bin exceeds a + * threshold. These TreeBins use a balanced tree to hold nodes (a + * specialized form of red-black trees), bounding search time to + * O(log N). Each search step in a TreeBin is at least twice as + * slow as in a regular list, but given that N cannot exceed + * (1<<64) (before running out of addresses) this bounds search + * steps, lock hold times, etc, to reasonable constants (roughly + * 100 nodes inspected per operation worst case) so long as keys + * are Comparable (which is very common -- String, Long, etc). + * TreeBin nodes (TreeNodes) also maintain the same "next" + * traversal pointers as regular nodes, so can be traversed in + * iterators in the same way. + * + * The table is resized when occupancy exceeds a percentage + * threshold (nominally, 0.75, but see below). Any thread + * noticing an overfull bin may assist in resizing after the + * initiating thread allocates and sets up the replacement + * array. However, rather than stalling, these other threads may + * proceed with insertions etc. The use of TreeBins shields us + * from the worst case effects of overfilling while resizes are in + * progress. Resizing proceeds by transferring bins, one by one, + * from the table to the next table. To enable concurrency, the + * next table must be (incrementally) prefilled with place-holders + * serving as reverse forwarders to the old table. Because we are + * using power-of-two expansion, the elements from each bin must + * either stay at same index, or move with a power of two + * offset. We eliminate unnecessary node creation by catching + * cases where old nodes can be reused because their next fields + * won't change. On average, only about one-sixth of them need + * cloning when a table doubles. The nodes they replace will be + * garbage collectable as soon as they are no longer referenced by + * any reader thread that may be in the midst of concurrently + * traversing table. Upon transfer, the old table bin contains + * only a special forwarding node (with hash field "MOVED") that + * contains the next table as its key. On encountering a + * forwarding node, access and update operations restart, using + * the new table. + * + * Each bin transfer requires its bin lock, which can stall + * waiting for locks while resizing. However, because other + * threads can join in and help resize rather than contend for + * locks, average aggregate waits become shorter as resizing + * progresses. The transfer operation must also ensure that all + * accessible bins in both the old and new table are usable by any + * traversal. This is arranged by proceeding from the last bin + * (table.length - 1) up towards the first. Upon seeing a + * forwarding node, traversals (see class Traverser) arrange to + * move to the new table without revisiting nodes. However, to + * ensure that no intervening nodes are skipped, bin splitting can + * only begin after the associated reverse-forwarders are in + * place. + * + * The traversal scheme also applies to partial traversals of + * ranges of bins (via an alternate Traverser constructor) + * to support partitioned aggregate operations. Also, read-only + * operations give up if ever forwarded to a null table, which + * provides support for shutdown-style clearing, which is also not + * currently implemented. + * + * Lazy table initialization minimizes footprint until first use, + * and also avoids resizings when the first operation is from a + * putAll, constructor with map argument, or deserialization. + * These cases attempt to override the initial capacity settings, + * but harmlessly fail to take effect in cases of races. + * + * The element count is maintained using a specialization of + * LongAdder. We need to incorporate a specialization rather than + * just use a LongAdder in order to access implicit + * contention-sensing that leads to creation of multiple + * CounterCells. The counter mechanics avoid contention on + * updates but can encounter cache thrashing if read too + * frequently during concurrent access. To avoid reading so often, + * resizing under contention is attempted only upon adding to a + * bin already holding two or more nodes. Under uniform hash + * distributions, the probability of this occurring at threshold + * is around 13%, meaning that only about 1 in 8 puts check + * threshold (and after resizing, many fewer do so). + * + * TreeBins use a special form of comparison for search and + * related operations (which is the main reason we cannot use + * existing collections such as TreeMaps). TreeBins contain + * Comparable elements, but may contain others, as well as + * elements that are Comparable but not necessarily Comparable + * for the same T, so we cannot invoke compareTo among them. To + * handle this, the tree is ordered primarily by hash value, then + * by Comparable.compareTo order if applicable. On lookup at a + * node, if elements are not comparable or compare as 0 then both + * left and right children may need to be searched in the case of + * tied hash values. (This corresponds to the full list search + * that would be necessary if all elements were non-Comparable and + * had tied hashes.) The red-black balancing code is updated from + * pre-jdk-collections + * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) + * based in turn on Cormen, Leiserson, and Rivest "Introduction to + * Algorithms" (CLR). + * + * TreeBins also require an additional locking mechanism. While + * list traversal is always possible by readers even during + * updates, tree traversal is not, mainly because of tree-rotations + * that may change the root node and/or its linkages. TreeBins + * include a simple read-write lock mechanism parasitic on the + * main bin-synchronization strategy: Structural adjustments + * associated with an insertion or removal are already bin-locked + * (and so cannot conflict with other writers) but must wait for + * ongoing readers to finish. Since there can be only one such + * waiter, we use a simple scheme using a single "waiter" field to + * block writers. However, readers need never block. If the root + * lock is held, they proceed along the slow traversal path (via + * next-pointers) until the lock becomes available or the list is + * exhausted, whichever comes first. These cases are not fast, but + * maximize aggregate expected throughput. + * + * Maintaining API and serialization compatibility with previous + * versions of this class introduces several oddities. Mainly: We + * leave untouched but unused constructor arguments refering to + * concurrencyLevel. We accept a loadFactor constructor argument, + * but apply it only to initial table capacity (which is the only + * time that we can guarantee to honor it.) We also declare an + * unused "Segment" class that is instantiated in minimal form + * only when serializing. + * + * This file is organized to make things a little easier to follow + * while reading than they might otherwise: First the main static + * declarations and utilities, then fields, then main public + * methods (with a few factorings of multiple public methods into + * internal ones), then sizing methods, trees, traversers, and + * bulk operations. */ /* ---------------- Constants -------------- */ /** - * The default initial capacity for this table, - * used when not otherwise specified in a constructor. + * The largest possible table capacity. This value must be + * exactly 1<<30 to stay within Java array allocation and indexing + * bounds for power of two table sizes, and is further required + * because the top two bits of 32bit hash fields are used for + * control purposes. */ - static final int DEFAULT_INITIAL_CAPACITY = 16; + private static final int MAXIMUM_CAPACITY = 1 << 30; /** - * The default load factor for this table, used when not - * otherwise specified in a constructor. + * The default initial table capacity. Must be a power of 2 + * (i.e., at least 1) and at most MAXIMUM_CAPACITY. */ - static final float DEFAULT_LOAD_FACTOR = 0.75f; + private static final int DEFAULT_CAPACITY = 16; /** - * The default concurrency level for this table, used when not - * otherwise specified in a constructor. + * The largest possible (non-power of two) array size. + * Needed by toArray and related methods. */ - static final int DEFAULT_CONCURRENCY_LEVEL = 16; + static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** - * The maximum capacity, used if a higher value is implicitly - * specified by either of the constructors with arguments. MUST - * be a power of two <= 1<<30 to ensure that entries are indexable - * using ints. + * The default concurrency level for this table. Unused but + * defined for compatibility with previous versions of this class. */ - static final int MAXIMUM_CAPACITY = 1 << 30; + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; /** - * The minimum capacity for per-segment tables. Must be a power - * of two, at least two to avoid immediate resizing on next use - * after lazy construction. + * The load factor for this table. Overrides of this value in + * constructors affect only the initial table capacity. The + * actual floating point value isn't normally used -- it is + * simpler to use expressions such as {@code n - (n >>> 2)} for + * the associated resizing threshold. */ - static final int MIN_SEGMENT_TABLE_CAPACITY = 2; + private static final float LOAD_FACTOR = 0.75f; /** - * The maximum number of segments to allow; used to bound - * constructor arguments. Must be power of two less than 1 << 24. + * The bin count threshold for using a tree rather than list for a + * bin. Bins are converted to trees when adding an element to a + * bin with at least this many nodes. The value must be greater + * than 2, and should be at least 8 to mesh with assumptions in + * tree removal about conversion back to plain bins upon + * shrinkage. */ - static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + static final int TREEIFY_THRESHOLD = 8; /** - * Number of unsynchronized retries in size and containsValue - * methods before resorting to locking. This is used to avoid - * unbounded retries if tables undergo continuous modification - * which would make it impossible to obtain an accurate result. + * The bin count threshold for untreeifying a (split) bin during a + * resize operation. Should be less than TREEIFY_THRESHOLD, and at + * most 6 to mesh with shrinkage detection under removal. */ - static final int RETRIES_BEFORE_LOCK = 2; - - /* ---------------- Fields -------------- */ + static final int UNTREEIFY_THRESHOLD = 6; /** - * Mask value for indexing into segments. The upper bits of a - * key's hash code are used to choose the segment. + * The smallest table capacity for which bins may be treeified. + * (Otherwise the table is resized if too many nodes in a bin.) + * The value should be at least 4 * TREEIFY_THRESHOLD to avoid + * conflicts between resizing and treeification thresholds. */ - final int segmentMask; + static final int MIN_TREEIFY_CAPACITY = 64; /** - * Shift value for indexing within segments. + * Minimum number of rebinnings per transfer step. Ranges are + * subdivided to allow multiple resizer threads. This value + * serves as a lower bound to avoid resizers encountering + * excessive memory contention. The value should be at least + * DEFAULT_CAPACITY. */ - final int segmentShift; + private static final int MIN_TRANSFER_STRIDE = 16; - /** - * The segments, each of which is a specialized hash table. + /* + * Encodings for Node hash fields. See above for explanation. */ - final Segment<K,V>[] segments; + static final int MOVED = 0x8fffffff; // (-1) hash for forwarding nodes + static final int TREEBIN = 0x80000000; // hash for roots of trees + static final int RESERVED = 0x80000001; // hash for transient reservations + static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash + + /** Number of CPUS, to place bounds on some sizings */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); - transient Set<K> keySet; - transient Set<Map.Entry<K,V>> entrySet; - transient Collection<V> values; + /** For serialization compatibility. */ + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("segments", Segment[].class), + new ObjectStreamField("segmentMask", Integer.TYPE), + new ObjectStreamField("segmentShift", Integer.TYPE) + }; + + /* ---------------- Nodes -------------- */ /** - * ConcurrentHashMap list entry. Note that this is never exported - * out as a user-visible Map.Entry. + * Key-value entry. This class is never exported out as a + * user-mutable Map.Entry (i.e., one supporting setValue; see + * MapEntry below), but can be used for read-only traversals used + * in bulk tasks. Subclasses of Node with a negative hash field + * are special, and contain null keys and values (but are never + * exported). Otherwise, keys and vals are never null. */ - static final class HashEntry<K,V> { + static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; - volatile V value; - volatile HashEntry<K,V> next; + volatile V val; + Node<K,V> next; - HashEntry(int hash, K key, V value, HashEntry<K,V> next) { + Node(int hash, K key, V val, Node<K,V> next) { this.hash = hash; this.key = key; - this.value = value; + this.val = val; this.next = next; } - /** - * Sets next field with volatile write semantics. (See above - * about use of putOrderedObject.) - */ - final void setNext(HashEntry<K,V> n) { - UNSAFE.putOrderedObject(this, nextOffset, n); + public final K getKey() { return key; } + public final V getValue() { return val; } + public final int hashCode() { return key.hashCode() ^ val.hashCode(); } + public final String toString(){ return key + "=" + val; } + public final V setValue(V value) { + throw new UnsupportedOperationException(); } - // Unsafe mechanics - static final sun.misc.Unsafe UNSAFE; - static final long nextOffset; - static { - try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class<?> k = HashEntry.class; - nextOffset = UNSAFE.objectFieldOffset - (k.getDeclaredField("next")); - } catch (Exception e) { - throw new Error(e); + public final boolean equals(Object o) { + Object k, v, u; Map.Entry<?,?> e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry<?,?>)o).getKey()) != null && + (v = e.getValue()) != null && + (k == key || k.equals(key)) && + (v == (u = val) || v.equals(u))); + } + + /** + * Virtualized support for map.get(); overridden in subclasses. + */ + Node<K,V> find(int h, Object k) { + Node<K,V> e = this; + if (k != null) { + do { + K ek; + if (e.hash == h && + ((ek = e.key) == k || (ek != null && k.equals(ek)))) + return e; + } while ((e = e.next) != null); } + return null; } } + /* ---------------- Static utilities -------------- */ + /** - * Gets the ith element of given table (if nonnull) with volatile - * read semantics. Note: This is manually integrated into a few - * performance-sensitive methods to reduce call overhead. + * Spreads (XORs) higher bits of hash to lower and also forces top + * bit to 0. Because the table uses power-of-two masking, sets of + * hashes that vary only in bits above the current mask will + * always collide. (Among known examples are sets of Float keys + * holding consecutive whole numbers in small tables.) So we + * apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed (so don't benefit from + * spreading), and because we use trees to handle large sets of + * collisions in bins, we just XOR some shifted bits in the + * cheapest possible way to reduce systematic lossage, as well as + * to incorporate impact of the highest bits that would otherwise + * never be used in index calculations because of table bounds. */ - @SuppressWarnings("unchecked") - static final <K,V> HashEntry<K,V> entryAt(HashEntry<K,V>[] tab, int i) { - return (tab == null) ? null : - (HashEntry<K,V>) UNSAFE.getObjectVolatile - (tab, ((long)i << TSHIFT) + TBASE); + static final int spread(int h) { + return (h ^ (h >>> 16)) & HASH_BITS; } /** - * Sets the ith element of given table, with volatile write - * semantics. (See above about use of putOrderedObject.) + * Returns a power of two table size for the given desired capacity. + * See Hackers Delight, sec 3.2 */ - static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i, - HashEntry<K,V> e) { - UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e); + private static final int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } + /** - * Applies a supplemental hash function to a given hashCode, which - * defends against poor quality hash functions. This is critical - * because ConcurrentHashMap uses power-of-two length hash tables, - * that otherwise encounter collisions for hashCodes that do not - * differ in lower or upper bits. + * Returns x's Class if it is of the form "class C implements + * Comparable<C>", else null. */ - private static int hash(int h) { - // Spread bits to regularize both segment and index locations, - // using variant of single-word Wang/Jenkins hash. - h += (h << 15) ^ 0xffffcd7d; - h ^= (h >>> 10); - h += (h << 3); - h ^= (h >>> 6); - h += (h << 2) + (h << 14); - return h ^ (h >>> 16); + static Class<?> comparableClassFor(Object x) { + if (x instanceof Comparable) { + Class<?> c; Type[] ts, as; Type t; ParameterizedType p; + if ((c = x.getClass()) == String.class) // bypass checks + return c; + if ((ts = c.getGenericInterfaces()) != null) { + for (int i = 0; i < ts.length; ++i) { + if (((t = ts[i]) instanceof ParameterizedType) && + ((p = (ParameterizedType)t).getRawType() == + Comparable.class) && + (as = p.getActualTypeArguments()) != null && + as.length == 1 && as[0] == c) // type arg is c + return c; + } + } + } + return null; } /** - * Segments are specialized versions of hash tables. This - * subclasses from ReentrantLock opportunistically, just to - * simplify some locking and avoid separate construction. + * Returns k.compareTo(x) if x matches kc (k's screened comparable + * class), else 0. */ - static final class Segment<K,V> extends ReentrantLock implements Serializable { - /* - * Segments maintain a table of entry lists that are always - * kept in a consistent state, so can be read (via volatile - * reads of segments and tables) without locking. This - * requires replicating nodes when necessary during table - * resizing, so the old lists can be traversed by readers - * still using old version of table. - * - * This class defines only mutative methods requiring locking. - * Except as noted, the methods of this class perform the - * per-segment versions of ConcurrentHashMap methods. (Other - * methods are integrated directly into ConcurrentHashMap - * methods.) These mutative methods use a form of controlled - * spinning on contention via methods scanAndLock and - * scanAndLockForPut. These intersperse tryLocks with - * traversals to locate nodes. The main benefit is to absorb - * cache misses (which are very common for hash tables) while - * obtaining locks so that traversal is faster once - * acquired. We do not actually use the found nodes since they - * must be re-acquired under lock anyway to ensure sequential - * consistency of updates (and in any case may be undetectably - * stale), but they will normally be much faster to re-locate. - * Also, scanAndLockForPut speculatively creates a fresh node - * to use in put if no node is found. - */ + @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable + static int compareComparables(Class<?> kc, Object k, Object x) { + return (x == null || x.getClass() != kc ? 0 : + ((Comparable)k).compareTo(x)); + } - private static final long serialVersionUID = 2249069246763182397L; + /* ---------------- Table element access -------------- */ - /** - * The maximum number of times to tryLock in a prescan before - * possibly blocking on acquire in preparation for a locked - * segment operation. On multiprocessors, using a bounded - * number of retries maintains cache acquired while locating - * nodes. - */ - static final int MAX_SCAN_RETRIES = - Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; + /* + * Volatile access methods are used for table elements as well as + * elements of in-progress next table while resizing. All uses of + * the tab arguments must be null checked by callers. All callers + * also paranoically precheck that tab's length is not zero (or an + * equivalent check), thus ensuring that any index argument taking + * the form of a hash value anded with (length - 1) is a valid + * index. Note that, to be correct wrt arbitrary concurrency + * errors by users, these checks must operate on local variables, + * which accounts for some odd-looking inline assignments below. + * Note that calls to setTabAt always occur within locked regions, + * and so do not need full volatile semantics, but still require + * ordering to maintain concurrent readability. + */ - /** - * The per-segment table. Elements are accessed via - * entryAt/setEntryAt providing volatile semantics. - */ - transient volatile HashEntry<K,V>[] table; + @SuppressWarnings("unchecked") + static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) { + return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } - /** - * The number of elements. Accessed only either within locks - * or among other volatile reads that maintain visibility. - */ - transient int count; + static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, + Node<K,V> c, Node<K,V> v) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } - /** - * The total number of mutative operations in this segment. - * Even though this may overflows 32 bits, it provides - * sufficient accuracy for stability checks in CHM isEmpty() - * and size() methods. Accessed only either within locks or - * among other volatile reads that maintain visibility. - */ - transient int modCount; + static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) { + U.putOrderedObject(tab, ((long)i << ASHIFT) + ABASE, v); + } - /** - * The table is rehashed when its size exceeds this threshold. - * (The value of this field is always <tt>(int)(capacity * - * loadFactor)</tt>.) - */ - transient int threshold; + /* ---------------- Fields -------------- */ - /** - * The load factor for the hash table. Even though this value - * is same for all segments, it is replicated to avoid needing - * links to outer object. - * @serial - */ - final float loadFactor; + /** + * The array of bins. Lazily initialized upon first insertion. + * Size is always a power of two. Accessed directly by iterators. + */ + transient volatile Node<K,V>[] table; - Segment(float lf, int threshold, HashEntry<K,V>[] tab) { - this.loadFactor = lf; - this.threshold = threshold; - this.table = tab; - } + /** + * The next table to use; non-null only while resizing. + */ + private transient volatile Node<K,V>[] nextTable; - final V put(K key, int hash, V value, boolean onlyIfAbsent) { - HashEntry<K,V> node = tryLock() ? null : - scanAndLockForPut(key, hash, value); - V oldValue; - try { - HashEntry<K,V>[] tab = table; - int index = (tab.length - 1) & hash; - HashEntry<K,V> first = entryAt(tab, index); - for (HashEntry<K,V> e = first;;) { - if (e != null) { - K k; - if ((k = e.key) == key || - (e.hash == hash && key.equals(k))) { - oldValue = e.value; - if (!onlyIfAbsent) { - e.value = value; - ++modCount; - } - break; - } - e = e.next; - } - else { - if (node != null) - node.setNext(first); - else - node = new HashEntry<K,V>(hash, key, value, first); - int c = count + 1; - if (c > threshold && tab.length < MAXIMUM_CAPACITY) - rehash(node); - else - setEntryAt(tab, index, node); - ++modCount; - count = c; - oldValue = null; - break; - } - } - } finally { - unlock(); - } - return oldValue; - } + /** + * Base counter value, used mainly when there is no contention, + * but also as a fallback during table initialization + * races. Updated via CAS. + */ + private transient volatile long baseCount; - /** - * Doubles size of table and repacks entries, also adding the - * given node to new table - */ - @SuppressWarnings("unchecked") - private void rehash(HashEntry<K,V> node) { - /* - * Reclassify nodes in each list to new table. Because we - * are using power-of-two expansion, the elements from - * each bin must either stay at same index, or move with a - * power of two offset. We eliminate unnecessary node - * creation by catching cases where old nodes can be - * reused because their next fields won't change. - * Statistically, at the default threshold, only about - * one-sixth of them need cloning when a table - * doubles. The nodes they replace will be garbage - * collectable as soon as they are no longer referenced by - * any reader thread that may be in the midst of - * concurrently traversing table. Entry accesses use plain - * array indexing because they are followed by volatile - * table write. - */ - HashEntry<K,V>[] oldTable = table; - int oldCapacity = oldTable.length; - int newCapacity = oldCapacity << 1; - threshold = (int)(newCapacity * loadFactor); - HashEntry<K,V>[] newTable = - (HashEntry<K,V>[]) new HashEntry<?,?>[newCapacity]; - int sizeMask = newCapacity - 1; - for (int i = 0; i < oldCapacity ; i++) { - HashEntry<K,V> e = oldTable[i]; - if (e != null) { - HashEntry<K,V> next = e.next; - int idx = e.hash & sizeMask; - if (next == null) // Single node on list - newTable[idx] = e; - else { // Reuse consecutive sequence at same slot - HashEntry<K,V> lastRun = e; - int lastIdx = idx; - for (HashEntry<K,V> last = next; - last != null; - last = last.next) { - int k = last.hash & sizeMask; - if (k != lastIdx) { - lastIdx = k; - lastRun = last; - } - } - newTable[lastIdx] = lastRun; - // Clone remaining nodes - for (HashEntry<K,V> p = e; p != lastRun; p = p.next) { - V v = p.value; - int h = p.hash; - int k = h & sizeMask; - HashEntry<K,V> n = newTable[k]; - newTable[k] = new HashEntry<K,V>(h, p.key, v, n); - } - } - } - } - int nodeIndex = node.hash & sizeMask; // add the new node - node.setNext(newTable[nodeIndex]); - newTable[nodeIndex] = node; - table = newTable; - } + /** + * Table initialization and resizing control. When negative, the + * table is being initialized or resized: -1 for initialization, + * else -(1 + the number of active resizing threads). Otherwise, + * when table is null, holds the initial table size to use upon + * creation, or 0 for default. After initialization, holds the + * next element count value upon which to resize the table. + */ + private transient volatile int sizeCtl; - /** - * Scans for a node containing given key while trying to - * acquire lock, creating and returning one if not found. Upon - * return, guarantees that lock is held. Unlike in most - * methods, calls to method equals are not screened: Since - * traversal speed doesn't matter, we might as well help warm - * up the associated code and accesses as well. - * - * @return a new node if key not found, else null - */ - private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) { - HashEntry<K,V> first = entryForHash(this, hash); - HashEntry<K,V> e = first; - HashEntry<K,V> node = null; - int retries = -1; // negative while locating node - while (!tryLock()) { - HashEntry<K,V> f; // to recheck first below - if (retries < 0) { - if (e == null) { - if (node == null) // speculatively create node - node = new HashEntry<K,V>(hash, key, value, null); - retries = 0; - } - else if (key.equals(e.key)) - retries = 0; - else - e = e.next; - } - else if (++retries > MAX_SCAN_RETRIES) { - lock(); - break; - } - else if ((retries & 1) == 0 && - (f = entryForHash(this, hash)) != first) { - e = first = f; // re-traverse if entry changed - retries = -1; - } - } - return node; - } + /** + * The next table index (plus one) to split while resizing. + */ + private transient volatile int transferIndex; - /** - * Scans for a node containing the given key while trying to - * acquire lock for a remove or replace operation. Upon - * return, guarantees that lock is held. Note that we must - * lock even if the key is not found, to ensure sequential - * consistency of updates. - */ - private void scanAndLock(Object key, int hash) { - // similar to but simpler than scanAndLockForPut - HashEntry<K,V> first = entryForHash(this, hash); - HashEntry<K,V> e = first; - int retries = -1; - while (!tryLock()) { - HashEntry<K,V> f; - if (retries < 0) { - if (e == null || key.equals(e.key)) - retries = 0; - else - e = e.next; - } - else if (++retries > MAX_SCAN_RETRIES) { - lock(); - break; - } - else if ((retries & 1) == 0 && - (f = entryForHash(this, hash)) != first) { - e = first = f; - retries = -1; - } - } - } + /** + * The least available table index to split while resizing. + */ + private transient volatile int transferOrigin; - /** - * Remove; match on key only if value null, else match both. - */ - final V remove(Object key, int hash, Object value) { - if (!tryLock()) - scanAndLock(key, hash); - V oldValue = null; - try { - HashEntry<K,V>[] tab = table; - int index = (tab.length - 1) & hash; - HashEntry<K,V> e = entryAt(tab, index); - HashEntry<K,V> pred = null; - while (e != null) { - K k; - HashEntry<K,V> next = e.next; - if ((k = e.key) == key || - (e.hash == hash && key.equals(k))) { - V v = e.value; - if (value == null || value == v || value.equals(v)) { - if (pred == null) - setEntryAt(tab, index, next); - else - pred.setNext(next); - ++modCount; - --count; - oldValue = v; - } - break; - } - pred = e; - e = next; - } - } finally { - unlock(); - } - return oldValue; - } + /** + * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. + */ + private transient volatile int cellsBusy; - final boolean replace(K key, int hash, V oldValue, V newValue) { - if (!tryLock()) - scanAndLock(key, hash); - boolean replaced = false; - try { - HashEntry<K,V> e; - for (e = entryForHash(this, hash); e != null; e = e.next) { - K k; - if ((k = e.key) == key || - (e.hash == hash && key.equals(k))) { - if (oldValue.equals(e.value)) { - e.value = newValue; - ++modCount; - replaced = true; - } - break; - } - } - } finally { - unlock(); - } - return replaced; - } + /** + * Table of counter cells. When non-null, size is a power of 2. + */ + private transient volatile CounterCell[] counterCells; - final V replace(K key, int hash, V value) { - if (!tryLock()) - scanAndLock(key, hash); - V oldValue = null; - try { - HashEntry<K,V> e; - for (e = entryForHash(this, hash); e != null; e = e.next) { - K k; - if ((k = e.key) == key || - (e.hash == hash && key.equals(k))) { - oldValue = e.value; - e.value = value; - ++modCount; - break; - } - } - } finally { - unlock(); - } - return oldValue; - } + // views + private transient KeySetView<K,V> keySet; + private transient ValuesView<K,V> values; + private transient EntrySetView<K,V> entrySet; - final void clear() { - lock(); - try { - HashEntry<K,V>[] tab = table; - for (int i = 0; i < tab.length ; i++) - setEntryAt(tab, i, null); - ++modCount; - count = 0; - } finally { - unlock(); - } - } - } - // Accessing segments + /* ---------------- Public operations -------------- */ /** - * Gets the jth element of given segment array (if nonnull) with - * volatile element access semantics via Unsafe. (The null check - * can trigger harmlessly only during deserialization.) Note: - * because each element of segments array is set only once (using - * fully ordered writes), some performance-sensitive methods rely - * on this method only as a recheck upon null reads. + * Creates a new, empty map with the default initial table size (16). */ - @SuppressWarnings("unchecked") - static final <K,V> Segment<K,V> segmentAt(Segment<K,V>[] ss, int j) { - long u = (j << SSHIFT) + SBASE; - return ss == null ? null : - (Segment<K,V>) UNSAFE.getObjectVolatile(ss, u); + public ConcurrentHashMap() { } /** - * Returns the segment for the given index, creating it and - * recording in segment table (via CAS) if not already present. + * Creates a new, empty map with an initial table size + * accommodating the specified number of elements without the need + * to dynamically resize. * - * @param k the index - * @return the segment - */ - @SuppressWarnings("unchecked") - private Segment<K,V> ensureSegment(int k) { - final Segment<K,V>[] ss = this.segments; - long u = (k << SSHIFT) + SBASE; // raw offset - Segment<K,V> seg; - if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) { - Segment<K,V> proto = ss[0]; // use segment 0 as prototype - int cap = proto.table.length; - float lf = proto.loadFactor; - int threshold = (int)(cap * lf); - HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry<?,?>[cap]; - if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) - == null) { // recheck - Segment<K,V> s = new Segment<K,V>(lf, threshold, tab); - while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) - == null) { - if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) - break; - } - } - } - return seg; - } - - // Hash-based segment and entry accesses - - /** - * Gets the segment for the given hash code. + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative */ - @SuppressWarnings("unchecked") - private Segment<K,V> segmentForHash(int h) { - long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; - return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u); + public ConcurrentHashMap(int initialCapacity) { + if (initialCapacity < 0) + throw new IllegalArgumentException(); + int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? + MAXIMUM_CAPACITY : + tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); + this.sizeCtl = cap; } /** - * Gets the table entry for the given segment and hash code. + * Creates a new map with the same mappings as the given map. + * + * @param m the map */ - @SuppressWarnings("unchecked") - static final <K,V> HashEntry<K,V> entryForHash(Segment<K,V> seg, int h) { - HashEntry<K,V>[] tab; - return (seg == null || (tab = seg.table) == null) ? null : - (HashEntry<K,V>) UNSAFE.getObjectVolatile - (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); + public ConcurrentHashMap(Map<? extends K, ? extends V> m) { + this.sizeCtl = DEFAULT_CAPACITY; + putAll(m); } - /* ---------------- Public operations -------------- */ - /** - * Creates a new, empty map with the specified initial - * capacity, load factor and concurrency level. + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}) and + * initial table density ({@code loadFactor}). * * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements. - * @param loadFactor the load factor threshold, used to control resizing. - * Resizing may be performed when the average number of elements per - * bin exceeds this threshold. - * @param concurrencyLevel the estimated number of concurrently - * updating threads. The implementation performs internal sizing - * to try to accommodate this many threads. - * @throws IllegalArgumentException if the initial capacity is - * negative or the load factor or concurrencyLevel are - * nonpositive. - */ - @SuppressWarnings("unchecked") - public ConcurrentHashMap(int initialCapacity, - float loadFactor, int concurrencyLevel) { - if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) - throw new IllegalArgumentException(); - if (concurrencyLevel > MAX_SEGMENTS) - concurrencyLevel = MAX_SEGMENTS; - // Find power-of-two sizes best matching arguments - int sshift = 0; - int ssize = 1; - while (ssize < concurrencyLevel) { - ++sshift; - ssize <<= 1; - } - this.segmentShift = 32 - sshift; - this.segmentMask = ssize - 1; - if (initialCapacity > MAXIMUM_CAPACITY) - initialCapacity = MAXIMUM_CAPACITY; - int c = initialCapacity / ssize; - if (c * ssize < initialCapacity) - ++c; - int cap = MIN_SEGMENT_TABLE_CAPACITY; - while (cap < c) - cap <<= 1; - // create segments and segments[0] - Segment<K,V> s0 = - new Segment<K,V>(loadFactor, (int)(cap * loadFactor), - (HashEntry<K,V>[])new HashEntry<?,?>[cap]); - Segment<K,V>[] ss = (Segment<K,V>[])new Segment<?,?>[ssize]; - UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] - this.segments = ss; - } - - /** - * Creates a new, empty map with the specified initial capacity - * and load factor and with the default concurrencyLevel (16). - * - * @param initialCapacity The implementation performs internal - * sizing to accommodate this many elements. - * @param loadFactor the load factor threshold, used to control resizing. - * Resizing may be performed when the average number of elements per - * bin exceeds this threshold. + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size * @throws IllegalArgumentException if the initial capacity of * elements is negative or the load factor is nonpositive * * @since 1.6 */ public ConcurrentHashMap(int initialCapacity, float loadFactor) { - this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL); + this(initialCapacity, loadFactor, 1); } /** - * Creates a new, empty map with the specified initial capacity, - * and with default load factor (0.75) and concurrencyLevel (16). + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}), table + * density ({@code loadFactor}), and number of concurrently + * updating threads ({@code concurrencyLevel}). * * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements. - * @throws IllegalArgumentException if the initial capacity of - * elements is negative. + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @param concurrencyLevel the estimated number of concurrently + * updating threads. The implementation may use this value as + * a sizing hint. + * @throws IllegalArgumentException if the initial capacity is + * negative or the load factor or concurrencyLevel are + * nonpositive */ - public ConcurrentHashMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + public ConcurrentHashMap(int initialCapacity, + float loadFactor, int concurrencyLevel) { + if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) + throw new IllegalArgumentException(); + if (initialCapacity < concurrencyLevel) // Use at least as many bins + initialCapacity = concurrencyLevel; // as estimated threads + long size = (long)(1.0 + (long)initialCapacity / loadFactor); + int cap = (size >= (long)MAXIMUM_CAPACITY) ? + MAXIMUM_CAPACITY : tableSizeFor((int)size); + this.sizeCtl = cap; } - /** - * Creates a new, empty map with a default initial capacity (16), - * load factor (0.75) and concurrencyLevel (16). - */ - public ConcurrentHashMap() { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); - } + // Original (since JDK1.2) Map methods /** - * Creates a new map with the same mappings as the given map. - * The map is created with a capacity of 1.5 times the number - * of mappings in the given map or 16 (whichever is greater), - * and a default load factor (0.75) and concurrencyLevel (16). - * - * @param m the map + * {@inheritDoc} */ - public ConcurrentHashMap(Map<? extends K, ? extends V> m) { - this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, - DEFAULT_INITIAL_CAPACITY), - DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); - putAll(m); + public int size() { + long n = sumCount(); + return ((n < 0L) ? 0 : + (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : + (int)n); } /** - * Returns <tt>true</tt> if this map contains no key-value mappings. - * - * @return <tt>true</tt> if this map contains no key-value mappings + * {@inheritDoc} */ public boolean isEmpty() { - /* - * Sum per-segment modCounts to avoid mis-reporting when - * elements are concurrently added and removed in one segment - * while checking another, in which case the table was never - * actually empty at any point. (The sum ensures accuracy up - * through at least 1<<31 per-segment modifications before - * recheck.) Methods size() and containsValue() use similar - * constructions for stability checks. - */ - long sum = 0L; - final Segment<K,V>[] segments = this.segments; - for (int j = 0; j < segments.length; ++j) { - Segment<K,V> seg = segmentAt(segments, j); - if (seg != null) { - if (seg.count != 0) - return false; - sum += seg.modCount; - } - } - if (sum != 0L) { // recheck unless no modifications - for (int j = 0; j < segments.length; ++j) { - Segment<K,V> seg = segmentAt(segments, j); - if (seg != null) { - if (seg.count != 0) - return false; - sum -= seg.modCount; - } - } - if (sum != 0L) - return false; - } - return true; - } - - /** - * Returns the number of key-value mappings in this map. If the - * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns - * <tt>Integer.MAX_VALUE</tt>. - * - * @return the number of key-value mappings in this map - */ - public int size() { - // Try a few times to get accurate count. On failure due to - // continuous async changes in table, resort to locking. - final Segment<K,V>[] segments = this.segments; - final int segmentCount = segments.length; - - long previousSum = 0L; - for (int retries = -1; retries < RETRIES_BEFORE_LOCK; retries++) { - long sum = 0L; // sum of modCounts - long size = 0L; - for (int i = 0; i < segmentCount; i++) { - Segment<K,V> segment = segmentAt(segments, i); - if (segment != null) { - sum += segment.modCount; - size += segment.count; - } - } - if (sum == previousSum) - return ((size >>> 31) == 0) ? (int) size : Integer.MAX_VALUE; - previousSum = sum; - } - - long size = 0L; - for (int i = 0; i < segmentCount; i++) { - Segment<K,V> segment = ensureSegment(i); - segment.lock(); - size += segment.count; - } - for (int i = 0; i < segmentCount; i++) - segments[i].unlock(); - return ((size >>> 31) == 0) ? (int) size : Integer.MAX_VALUE; + return sumCount() <= 0L; // ignore transient negative values } /** @@ -878,18 +742,20 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @throws NullPointerException if the specified key is null */ public V get(Object key) { - Segment<K,V> s; // manually integrate access methods to reduce overhead - HashEntry<K,V>[] tab; - int h = hash(key.hashCode()); - long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; - if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null && - (tab = s.table) != null) { - for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile - (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); - e != null; e = e.next) { - K k; - if ((k = e.key) == key || (e.hash == h && key.equals(k))) - return e.value; + Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; + int h = spread(key.hashCode()); + if ((tab = table) != null && (n = tab.length) > 0 && + (e = tabAt(tab, (n - 1) & h)) != null) { + if ((eh = e.hash) == h) { + if ((ek = e.key) == key || (ek != null && key.equals(ek))) + return e.val; + } + else if (eh < 0) + return (p = e.find(h, key)) != null ? p.val : null; + while ((e = e.next) != null) { + if (e.hash == h && + ((ek = e.key) == key || (ek != null && key.equals(ek)))) + return e.val; } } return null; @@ -898,149 +764,121 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> /** * Tests if the specified object is a key in this table. * - * @param key possible key - * @return <tt>true</tt> if and only if the specified object + * @param key possible key + * @return {@code true} if and only if the specified object * is a key in this table, as determined by the - * <tt>equals</tt> method; <tt>false</tt> otherwise. + * {@code equals} method; {@code false} otherwise * @throws NullPointerException if the specified key is null */ - @SuppressWarnings("unchecked") public boolean containsKey(Object key) { - Segment<K,V> s; // same as get() except no need for volatile value read - HashEntry<K,V>[] tab; - int h = hash(key.hashCode()); - long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; - if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null && - (tab = s.table) != null) { - for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile - (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); - e != null; e = e.next) { - K k; - if ((k = e.key) == key || (e.hash == h && key.equals(k))) - return true; - } - } - return false; + return get(key) != null; } /** - * Returns <tt>true</tt> if this map maps one or more keys to the - * specified value. Note: This method requires a full internal - * traversal of the hash table, and so is much slower than - * method <tt>containsKey</tt>. + * Returns {@code true} if this map maps one or more keys to the + * specified value. Note: This method may require a full traversal + * of the map, and is much slower than method {@code containsKey}. * * @param value value whose presence in this map is to be tested - * @return <tt>true</tt> if this map maps one or more keys to the + * @return {@code true} if this map maps one or more keys to the * specified value * @throws NullPointerException if the specified value is null */ public boolean containsValue(Object value) { - // Same idea as size() if (value == null) throw new NullPointerException(); - final Segment<K,V>[] segments = this.segments; - long previousSum = 0L; - int lockCount = 0; - try { - for (int retries = -1; ; retries++) { - long sum = 0L; // sum of modCounts - for (int j = 0; j < segments.length; j++) { - Segment<K,V> segment; - if (retries == RETRIES_BEFORE_LOCK) { - segment = ensureSegment(j); - segment.lock(); - lockCount++; - } else { - segment = segmentAt(segments, j); - if (segment == null) - continue; - } - HashEntry<K,V>[] tab = segment.table; - if (tab != null) { - for (int i = 0 ; i < tab.length; i++) { - HashEntry<K,V> e; - for (e = entryAt(tab, i); e != null; e = e.next) { - V v = e.value; - if (v != null && value.equals(v)) - return true; - } - } - sum += segment.modCount; - } - } - if ((retries >= 0 && sum == previousSum) || lockCount > 0) - return false; - previousSum = sum; + Node<K,V>[] t; + if ((t = table) != null) { + Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length); + for (Node<K,V> p; (p = it.advance()) != null; ) { + V v; + if ((v = p.val) == value || (v != null && value.equals(v))) + return true; } - } finally { - for (int j = 0; j < lockCount; j++) - segments[j].unlock(); } - } - - /** - * Legacy method testing if some key maps into the specified value - * in this table. This method is identical in functionality to - * {@link #containsValue}, and exists solely to ensure - * full compatibility with class {@link java.util.Hashtable}, - * which supported this method prior to introduction of the - * Java Collections framework. - * - * @param value a value to search for - * @return <tt>true</tt> if and only if some key maps to the - * <tt>value</tt> argument in this table as - * determined by the <tt>equals</tt> method; - * <tt>false</tt> otherwise - * @throws NullPointerException if the specified value is null - */ - public boolean contains(Object value) { - return containsValue(value); + return false; } /** * Maps the specified key to the specified value in this table. * Neither the key nor the value can be null. * - * <p> The value can be retrieved by calling the <tt>get</tt> method + * <p>The value can be retrieved by calling the {@code get} method * with a key that is equal to the original key. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key - * @return the previous value associated with <tt>key</tt>, or - * <tt>null</tt> if there was no mapping for <tt>key</tt> + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} * @throws NullPointerException if the specified key or value is null */ - @SuppressWarnings("unchecked") public V put(K key, V value) { - Segment<K,V> s; - if (value == null) - throw new NullPointerException(); - int hash = hash(key.hashCode()); - int j = (hash >>> segmentShift) & segmentMask; - if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck - (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment - s = ensureSegment(j); - return s.put(key, hash, value, false); + return putVal(key, value, false); } - /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, - * or <tt>null</tt> if there was no mapping for the key - * @throws NullPointerException if the specified key or value is null - */ - @SuppressWarnings("unchecked") - public V putIfAbsent(K key, V value) { - Segment<K,V> s; - if (value == null) - throw new NullPointerException(); - int hash = hash(key.hashCode()); - int j = (hash >>> segmentShift) & segmentMask; - if ((s = (Segment<K,V>)UNSAFE.getObject - (segments, (j << SSHIFT) + SBASE)) == null) - s = ensureSegment(j); - return s.put(key, hash, value, true); + /** Implementation for put and putIfAbsent */ + final V putVal(K key, V value, boolean onlyIfAbsent) { + if (key == null || value == null) throw new NullPointerException(); + int hash = spread(key.hashCode()); + int binCount = 0; + for (Node<K,V>[] tab = table;;) { + Node<K,V> f; int n, i, fh; + if (tab == null || (n = tab.length) == 0) + tab = initTable(); + else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { + if (casTabAt(tab, i, null, + new Node<K,V>(hash, key, value, null))) + break; // no lock when adding to empty bin + } + else if ((fh = f.hash) == MOVED) + tab = helpTransfer(tab, f); + else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node<K,V> e = f;; ++binCount) { + K ek; + if (e.hash == hash && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + oldVal = e.val; + if (!onlyIfAbsent) + e.val = value; + break; + } + Node<K,V> pred = e; + if ((e = e.next) == null) { + pred.next = new Node<K,V>(hash, key, + value, null); + break; + } + } + } + else if (f instanceof TreeBin) { + Node<K,V> p; + binCount = 2; + if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, + value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) + p.val = value; + } + } + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) + treeifyBin(tab, i); + if (oldVal != null) + return oldVal; + break; + } + } + } + addCount(1L, binCount); + return null; } /** @@ -1051,8 +889,9 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @param m mappings to be stored in this map */ public void putAll(Map<? extends K, ? extends V> m) { + tryPresize(m.size()); for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) - put(e.getKey(), e.getValue()); + putVal(e.getKey(), e.getValue(), false); } /** @@ -1060,87 +899,147 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * This method does nothing if the key is not in the map. * * @param key the key that needs to be removed - * @return the previous value associated with <tt>key</tt>, or - * <tt>null</tt> if there was no mapping for <tt>key</tt> + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} * @throws NullPointerException if the specified key is null */ public V remove(Object key) { - int hash = hash(key.hashCode()); - Segment<K,V> s = segmentForHash(hash); - return s == null ? null : s.remove(key, hash, null); - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if the specified key is null - */ - public boolean remove(Object key, Object value) { - int hash = hash(key.hashCode()); - Segment<K,V> s; - return value != null && (s = segmentForHash(hash)) != null && - s.remove(key, hash, value) != null; - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if any of the arguments are null - */ - public boolean replace(K key, V oldValue, V newValue) { - int hash = hash(key.hashCode()); - if (oldValue == null || newValue == null) - throw new NullPointerException(); - Segment<K,V> s = segmentForHash(hash); - return s != null && s.replace(key, hash, oldValue, newValue); + return replaceNode(key, null, null); } /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, - * or <tt>null</tt> if there was no mapping for the key - * @throws NullPointerException if the specified key or value is null + * Implementation for the four public remove/replace methods: + * Replaces node value with v, conditional upon match of cv if + * non-null. If resulting value is null, delete. */ - public V replace(K key, V value) { - int hash = hash(key.hashCode()); - if (value == null) - throw new NullPointerException(); - Segment<K,V> s = segmentForHash(hash); - return s == null ? null : s.replace(key, hash, value); + final V replaceNode(Object key, V value, Object cv) { + int hash = spread(key.hashCode()); + for (Node<K,V>[] tab = table;;) { + Node<K,V> f; int n, i, fh; + if (tab == null || (n = tab.length) == 0 || + (f = tabAt(tab, i = (n - 1) & hash)) == null) + break; + else if ((fh = f.hash) == MOVED) + tab = helpTransfer(tab, f); + else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + validated = true; + for (Node<K,V> e = f, pred = null;;) { + K ek; + if (e.hash == hash && + ((ek = e.key) == key || + (ek != null && key.equals(ek)))) { + V ev = e.val; + if (cv == null || cv == ev || + (ev != null && cv.equals(ev))) { + oldVal = ev; + if (value != null) + e.val = value; + else if (pred != null) + pred.next = e.next; + else + setTabAt(tab, i, e.next); + } + break; + } + pred = e; + if ((e = e.next) == null) + break; + } + } + else if (f instanceof TreeBin) { + validated = true; + TreeBin<K,V> t = (TreeBin<K,V>)f; + TreeNode<K,V> r, p; + if ((r = t.root) != null && + (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || + (pv != null && cv.equals(pv))) { + oldVal = pv; + if (value != null) + p.val = value; + else if (t.removeTreeNode(p)) + setTabAt(tab, i, untreeify(t.first)); + } + } + } + } + } + if (validated) { + if (oldVal != null) { + if (value == null) + addCount(-1L, -1); + return oldVal; + } + break; + } + } + } + return null; } /** * Removes all of the mappings from this map. */ public void clear() { - final Segment<K,V>[] segments = this.segments; - for (int j = 0; j < segments.length; ++j) { - Segment<K,V> s = segmentAt(segments, j); - if (s != null) - s.clear(); + long delta = 0L; // negative number of deletions + int i = 0; + Node<K,V>[] tab = table; + while (tab != null && i < tab.length) { + int fh; + Node<K,V> f = tabAt(tab, i); + if (f == null) + ++i; + else if ((fh = f.hash) == MOVED) { + tab = helpTransfer(tab, f); + i = 0; // restart + } + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + Node<K,V> p = (fh >= 0 ? f : + (f instanceof TreeBin) ? + ((TreeBin<K,V>)f).first : null); + while (p != null) { + --delta; + p = p.next; + } + setTabAt(tab, i++, null); + } + } + } } + if (delta != 0L) + addCount(delta, -1); } /** * Returns a {@link Set} view of the keys contained in this map. * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. The set supports element + * reflected in the set, and vice-versa. The set supports element * removal, which removes the corresponding mapping from this map, - * via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, - * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> - * operations. It does not support the <tt>add</tt> or - * <tt>addAll</tt> operations. + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or + * {@code addAll} operations. * - * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator + * <p>The view's {@code iterator} is a "weakly consistent" iterator * that will never throw {@link ConcurrentModificationException}, * and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) * reflect any modifications subsequent to construction. + * + * @return the set view + * */ public Set<K> keySet() { - Set<K> ks = keySet; - return (ks != null) ? ks : (keySet = new KeySet()); + KeySetView<K,V> ks; + return (ks = keySet) != null ? ks : (keySet = new KeySetView<K,V>(this, null)); } /** @@ -1148,20 +1047,22 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * The collection is backed by the map, so changes to the map are * reflected in the collection, and vice-versa. The collection * supports element removal, which removes the corresponding - * mapping from this map, via the <tt>Iterator.remove</tt>, - * <tt>Collection.remove</tt>, <tt>removeAll</tt>, - * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not - * support the <tt>add</tt> or <tt>addAll</tt> operations. + * mapping from this map, via the {@code Iterator.remove}, + * {@code Collection.remove}, {@code removeAll}, + * {@code retainAll}, and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. * - * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator + * <p>The view's {@code iterator} is a "weakly consistent" iterator * that will never throw {@link ConcurrentModificationException}, * and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) * reflect any modifications subsequent to construction. + * + * @return the collection view */ public Collection<V> values() { - Collection<V> vs = values; - return (vs != null) ? vs : (values = new Values()); + ValuesView<K,V> vs; + return (vs = values) != null ? vs : (values = new ValuesView<K,V>(this)); } /** @@ -1169,20 +1070,329 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. The set supports element * removal, which removes the corresponding mapping from the map, - * via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, - * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> - * operations. It does not support the <tt>add</tt> or - * <tt>addAll</tt> operations. + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. * - * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator + * <p>The view's {@code iterator} is a "weakly consistent" iterator * that will never throw {@link ConcurrentModificationException}, * and guarantees to traverse elements as they existed upon * construction of the iterator, and may (but is not guaranteed to) * reflect any modifications subsequent to construction. + * + * @return the set view */ public Set<Map.Entry<K,V>> entrySet() { - Set<Map.Entry<K,V>> es = entrySet; - return (es != null) ? es : (entrySet = new EntrySet()); + EntrySetView<K,V> es; + return (es = entrySet) != null ? es : (entrySet = new EntrySetView<K,V>(this)); + } + + /** + * Returns the hash code value for this {@link Map}, i.e., + * the sum of, for each key-value pair in the map, + * {@code key.hashCode() ^ value.hashCode()}. + * + * @return the hash code value for this map + */ + public int hashCode() { + int h = 0; + Node<K,V>[] t; + if ((t = table) != null) { + Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length); + for (Node<K,V> p; (p = it.advance()) != null; ) + h += p.key.hashCode() ^ p.val.hashCode(); + } + return h; + } + + /** + * Returns a string representation of this map. The string + * representation consists of a list of key-value mappings (in no + * particular order) enclosed in braces ("{@code {}}"). Adjacent + * mappings are separated by the characters {@code ", "} (comma + * and space). Each key-value mapping is rendered as the key + * followed by an equals sign ("{@code =}") followed by the + * associated value. + * + * @return a string representation of this map + */ + public String toString() { + Node<K,V>[] t; + int f = (t = table) == null ? 0 : t.length; + Traverser<K,V> it = new Traverser<K,V>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Node<K,V> p; + if ((p = it.advance()) != null) { + for (;;) { + K k = p.key; + V v = p.val; + sb.append(k == this ? "(this Map)" : k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) + break; + sb.append(',').append(' '); + } + } + return sb.append('}').toString(); + } + + /** + * Compares the specified object with this map for equality. + * Returns {@code true} if the given object is a map with the same + * mappings as this map. This operation may return misleading + * results if either map is concurrently modified during execution + * of this method. + * + * @param o object to be compared for equality with this map + * @return {@code true} if the specified object is equal to this map + */ + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Map)) + return false; + Map<?,?> m = (Map<?,?>) o; + Node<K,V>[] t; + int f = (t = table) == null ? 0 : t.length; + Traverser<K,V> it = new Traverser<K,V>(t, f, 0, f); + for (Node<K,V> p; (p = it.advance()) != null; ) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || (v != val && !v.equals(val))) + return false; + } + for (Map.Entry<?,?> e : m.entrySet()) { + Object mk, mv, v; + if ((mk = e.getKey()) == null || + (mv = e.getValue()) == null || + (v = get(mk)) == null || + (mv != v && !mv.equals(v))) + return false; + } + } + return true; + } + + /** + * Stripped-down version of helper class used in previous version, + * declared for the sake of serialization compatibility + */ + static class Segment<K,V> extends ReentrantLock implements Serializable { + private static final long serialVersionUID = 2249069246763182397L; + final float loadFactor; + Segment(float lf) { this.loadFactor = lf; } + } + + /** + * Saves the state of the {@code ConcurrentHashMap} instance to a + * stream (i.e., serializes it). + * @param s the stream + * @serialData + * the key (Object) and value (Object) + * for each key-value mapping, followed by a null pair. + * The key-value mappings are emitted in no particular order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // For serialization compatibility + // Emulate segment calculation from previous version of this class + int sshift = 0; + int ssize = 1; + while (ssize < DEFAULT_CONCURRENCY_LEVEL) { + ++sshift; + ssize <<= 1; + } + int segmentShift = 32 - sshift; + int segmentMask = ssize - 1; + @SuppressWarnings("unchecked") Segment<K,V>[] segments = (Segment<K,V>[]) + new Segment<?,?>[DEFAULT_CONCURRENCY_LEVEL]; + for (int i = 0; i < segments.length; ++i) + segments[i] = new Segment<K,V>(LOAD_FACTOR); + s.putFields().put("segments", segments); + s.putFields().put("segmentShift", segmentShift); + s.putFields().put("segmentMask", segmentMask); + s.writeFields(); + + Node<K,V>[] t; + if ((t = table) != null) { + Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length); + for (Node<K,V> p; (p = it.advance()) != null; ) { + s.writeObject(p.key); + s.writeObject(p.val); + } + } + s.writeObject(null); + s.writeObject(null); + segments = null; // throw away + } + + /** + * Reconstitutes the instance from a stream (that is, deserializes it). + * @param s the stream + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + /* + * To improve performance in typical cases, we create nodes + * while reading, then place in table once size is known. + * However, we must also validate uniqueness and deal with + * overpopulated bins while doing so, which requires + * specialized versions of putVal mechanics. + */ + sizeCtl = -1; // force exclusion for table construction + s.defaultReadObject(); + long size = 0L; + Node<K,V> p = null; + for (;;) { + @SuppressWarnings("unchecked") K k = (K) s.readObject(); + @SuppressWarnings("unchecked") V v = (V) s.readObject(); + if (k != null && v != null) { + p = new Node<K,V>(spread(k.hashCode()), k, v, p); + ++size; + } + else + break; + } + if (size == 0L) + sizeCtl = 0; + else { + int n; + if (size >= (long)(MAXIMUM_CAPACITY >>> 1)) + n = MAXIMUM_CAPACITY; + else { + int sz = (int)size; + n = tableSizeFor(sz + (sz >>> 1) + 1); + } + @SuppressWarnings({"rawtypes","unchecked"}) + Node<K,V>[] tab = (Node<K,V>[])new Node[n]; + int mask = n - 1; + long added = 0L; + while (p != null) { + boolean insertAtFront; + Node<K,V> next = p.next, first; + int h = p.hash, j = h & mask; + if ((first = tabAt(tab, j)) == null) + insertAtFront = true; + else { + K k = p.key; + if (first.hash < 0) { + TreeBin<K,V> t = (TreeBin<K,V>)first; + if (t.putTreeVal(h, k, p.val) == null) + ++added; + insertAtFront = false; + } + else { + int binCount = 0; + insertAtFront = true; + Node<K,V> q; K qk; + for (q = first; q != null; q = q.next) { + if (q.hash == h && + ((qk = q.key) == k || + (qk != null && k.equals(qk)))) { + insertAtFront = false; + break; + } + ++binCount; + } + if (insertAtFront && binCount >= TREEIFY_THRESHOLD) { + insertAtFront = false; + ++added; + p.next = first; + TreeNode<K,V> hd = null, tl = null; + for (q = p; q != null; q = q.next) { + TreeNode<K,V> t = new TreeNode<K,V> + (q.hash, q.key, q.val, null, null); + if ((t.prev = tl) == null) + hd = t; + else + tl.next = t; + tl = t; + } + setTabAt(tab, j, new TreeBin<K,V>(hd)); + } + } + } + if (insertAtFront) { + ++added; + p.next = first; + setTabAt(tab, j, p); + } + p = next; + } + table = tab; + sizeCtl = n - (n >>> 2); + baseCount = added; + } + } + + // ConcurrentMap methods + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + public V putIfAbsent(K key, V value) { + return putVal(key, value, true); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object key, Object value) { + if (key == null) + throw new NullPointerException(); + return value != null && replaceNode(key, null, value) != null; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + public boolean replace(K key, V oldValue, V newValue) { + if (key == null || oldValue == null || newValue == null) + throw new NullPointerException(); + return replaceNode(key, newValue, oldValue) != null; + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + public V replace(K key, V value) { + if (key == null || value == null) + throw new NullPointerException(); + return replaceNode(key, value, null); + } + // Hashtable legacy methods + + /** + * Legacy method testing if some key maps into the specified value + * in this table. This method is identical in functionality to + * {@link #containsValue(Object)}, and exists solely to ensure + * full compatibility with class {@link java.util.Hashtable}. + * + * @param value a value to search for + * @return {@code true} if and only if some key maps to the + * {@code value} argument in this table as + * determined by the {@code equals} method; + * {@code false} otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + // BEGIN android-note + // removed deprecation + // END android-note + return containsValue(value); } /** @@ -1192,7 +1402,9 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @see #keySet() */ public Enumeration<K> keys() { - return new KeyIterator(); + Node<K,V>[] t; + int f = (t = table) == null ? 0 : t.length; + return new KeyIterator<K,V>(t, f, 0, f, this); } /** @@ -1202,281 +1414,1787 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> * @see #values() */ public Enumeration<V> elements() { - return new ValueIterator(); + Node<K,V>[] t; + int f = (t = table) == null ? 0 : t.length; + return new ValueIterator<K,V>(t, f, 0, f, this); } - /* ---------------- Iterator Support -------------- */ + // ConcurrentHashMap-only methods - abstract class HashIterator { - int nextSegmentIndex; - int nextTableIndex; - HashEntry<K,V>[] currentTable; - HashEntry<K, V> nextEntry; - HashEntry<K, V> lastReturned; + /** + * Returns the number of mappings. This method should be used + * instead of {@link #size} because a ConcurrentHashMap may + * contain more mappings than can be represented as an int. The + * value returned is an estimate; the actual count may differ if + * there are concurrent insertions or removals. + * + * @return the number of mappings + * @since 1.8 + * + * @hide + */ + public long mappingCount() { + long n = sumCount(); + return (n < 0L) ? 0L : n; // ignore transient negative values + } - HashIterator() { - nextSegmentIndex = segments.length - 1; - nextTableIndex = -1; - advance(); + /** + * Creates a new {@link Set} backed by a ConcurrentHashMap + * from the given type to {@code Boolean.TRUE}. + * + * @return the new set + * @since 1.8 + * + * @hide + */ + public static <K> KeySetView<K,Boolean> newKeySet() { + return new KeySetView<K,Boolean> + (new ConcurrentHashMap<K,Boolean>(), Boolean.TRUE); + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMap + * from the given type to {@code Boolean.TRUE}. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + * @return the new set + * @since 1.8 + * + * @hide + */ + public static <K> KeySetView<K,Boolean> newKeySet(int initialCapacity) { + return new KeySetView<K,Boolean> + (new ConcurrentHashMap<K,Boolean>(initialCapacity), Boolean.TRUE); + } + + /** + * Returns a {@link Set} view of the keys in this map, using the + * given common mapped value for any additions (i.e., {@link + * Collection#add} and {@link Collection#addAll(Collection)}). + * This is of course only appropriate if it is acceptable to use + * the same value for all additions from this view. + * + * @param mappedValue the mapped value to use for any additions + * @return the set view + * @throws NullPointerException if the mappedValue is null + * + * @hide + */ + public Set<K> keySet(V mappedValue) { + if (mappedValue == null) + throw new NullPointerException(); + return new KeySetView<K,V>(this, mappedValue); + } + + /* ---------------- Special Nodes -------------- */ + + /** + * A node inserted at head of bins during transfer operations. + */ + static final class ForwardingNode<K,V> extends Node<K,V> { + final Node<K,V>[] nextTable; + ForwardingNode(Node<K,V>[] tab) { + super(MOVED, null, null, null); + this.nextTable = tab; + } + + Node<K,V> find(int h, Object k) { + Node<K,V> e; int n; + Node<K,V>[] tab = nextTable; + if (k != null && tab != null && (n = tab.length) > 0 && + (e = tabAt(tab, (n - 1) & h)) != null) { + do { + int eh; K ek; + if ((eh = e.hash) == h && + ((ek = e.key) == k || (ek != null && k.equals(ek)))) + return e; + if (eh < 0) + return e.find(h, k); + } while ((e = e.next) != null); + } + return null; + } + } + + /** + * A place-holder node used in computeIfAbsent and compute + */ + static final class ReservationNode<K,V> extends Node<K,V> { + ReservationNode() { + super(RESERVED, null, null, null); + } + + Node<K,V> find(int h, Object k) { + return null; + } + } + + /* ---------------- Table Initialization and Resizing -------------- */ + + /** + * Initializes table, using the size recorded in sizeCtl. + */ + private final Node<K,V>[] initTable() { + Node<K,V>[] tab; int sc; + while ((tab = table) == null || tab.length == 0) { + if ((sc = sizeCtl) < 0) + Thread.yield(); // lost initialization race; just spin + else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if ((tab = table) == null || tab.length == 0) { + int n = (sc > 0) ? sc : DEFAULT_CAPACITY; + @SuppressWarnings({"rawtypes","unchecked"}) + Node<K,V>[] nt = (Node<K,V>[])new Node[n]; + table = tab = nt; + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + break; + } + } + return tab; + } + + /** + * Adds to count, and if table is too small and not already + * resizing, initiates transfer. If already resizing, helps + * perform transfer if work is available. Rechecks occupancy + * after a transfer to see if another resize is already needed + * because resizings are lagging additions. + * + * @param x the count to add + * @param check if <0, don't check resize, if <= 1 only check if uncontended + */ + private final void addCount(long x, int check) { + CounterCell[] as; long b, s; + if ((as = counterCells) != null || + !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { + CounterHashCode hc; CounterCell a; long v; int m; + boolean uncontended = true; + if ((hc = threadCounterHashCode.get()) == null || + as == null || (m = as.length - 1) < 0 || + (a = as[m & hc.code]) == null || + !(uncontended = + U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { + fullAddCount(x, hc, uncontended); + return; + } + if (check <= 1) + return; + s = sumCount(); + } + if (check >= 0) { + Node<K,V>[] tab, nt; int sc; + while (s >= (long)(sc = sizeCtl) && (tab = table) != null && + tab.length < MAXIMUM_CAPACITY) { + if (sc < 0) { + if (sc == -1 || transferIndex <= transferOrigin || + (nt = nextTable) == null) + break; + if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) + transfer(tab, nt); + } + else if (U.compareAndSwapInt(this, SIZECTL, sc, -2)) + transfer(tab, null); + s = sumCount(); + } + } + } + + /** + * Helps transfer if a resize is in progress. + */ + final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { + Node<K,V>[] nextTab; int sc; + if ((f instanceof ForwardingNode) && + (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { + if (nextTab == nextTable && tab == table && + transferIndex > transferOrigin && (sc = sizeCtl) < -1 && + U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) + transfer(tab, nextTab); + return nextTab; + } + return table; + } + + /** + * Tries to presize table to accommodate the given number of elements. + * + * @param size number of elements (doesn't need to be perfectly accurate) + */ + private final void tryPresize(int size) { + int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : + tableSizeFor(size + (size >>> 1) + 1); + int sc; + while ((sc = sizeCtl) >= 0) { + Node<K,V>[] tab = table; int n; + if (tab == null || (n = tab.length) == 0) { + n = (sc > c) ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (table == tab) { + @SuppressWarnings({"rawtypes","unchecked"}) + Node<K,V>[] nt = (Node<K,V>[])new Node[n]; + table = nt; + sc = n - (n >>> 2); + } + } finally { + sizeCtl = sc; + } + } + } + else if (c <= sc || n >= MAXIMUM_CAPACITY) + break; + else if (tab == table && + U.compareAndSwapInt(this, SIZECTL, sc, -2)) + transfer(tab, null); + } + } + + /** + * Moves and/or copies the nodes in each bin to new table. See + * above for explanation. + */ + private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { + int n = tab.length, stride; + if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) + stride = MIN_TRANSFER_STRIDE; // subdivide range + if (nextTab == null) { // initiating + try { + @SuppressWarnings({"rawtypes","unchecked"}) + Node<K,V>[] nt = (Node<K,V>[])new Node[n << 1]; + nextTab = nt; + } catch (Throwable ex) { // try to cope with OOME + sizeCtl = Integer.MAX_VALUE; + return; + } + nextTable = nextTab; + transferOrigin = n; + transferIndex = n; + ForwardingNode<K,V> rev = new ForwardingNode<K,V>(tab); + for (int k = n; k > 0;) { // progressively reveal ready slots + int nextk = (k > stride) ? k - stride : 0; + for (int m = nextk; m < k; ++m) + nextTab[m] = rev; + for (int m = n + nextk; m < n + k; ++m) + nextTab[m] = rev; + U.putOrderedInt(this, TRANSFERORIGIN, k = nextk); + } + } + int nextn = nextTab.length; + ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); + boolean advance = true; + for (int i = 0, bound = 0;;) { + int nextIndex, nextBound, fh; Node<K,V> f; + while (advance) { + if (--i >= bound) + advance = false; + else if ((nextIndex = transferIndex) <= transferOrigin) { + i = -1; + advance = false; + } + else if (U.compareAndSwapInt + (this, TRANSFERINDEX, nextIndex, + nextBound = (nextIndex > stride ? + nextIndex - stride : 0))) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + if (i < 0 || i >= n || i + n >= nextn) { + for (int sc;;) { + if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, ++sc)) { + if (sc == -1) { + nextTable = null; + table = nextTab; + sizeCtl = (n << 1) - (n >>> 1); + } + return; + } + } + } + else if ((f = tabAt(tab, i)) == null) { + if (casTabAt(tab, i, null, fwd)) { + setTabAt(nextTab, i, null); + setTabAt(nextTab, i + n, null); + advance = true; + } + } + else if ((fh = f.hash) == MOVED) + advance = true; // already processed + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + Node<K,V> ln, hn; + if (fh >= 0) { + int runBit = fh & n; + Node<K,V> lastRun = f; + for (Node<K,V> p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + if (runBit == 0) { + ln = lastRun; + hn = null; + } + else { + hn = lastRun; + ln = null; + } + for (Node<K,V> p = f; p != lastRun; p = p.next) { + int ph = p.hash; K pk = p.key; V pv = p.val; + if ((ph & n) == 0) + ln = new Node<K,V>(ph, pk, pv, ln); + else + hn = new Node<K,V>(ph, pk, pv, hn); + } + } + else if (f instanceof TreeBin) { + TreeBin<K,V> t = (TreeBin<K,V>)f; + TreeNode<K,V> lo = null, loTail = null; + TreeNode<K,V> hi = null, hiTail = null; + int lc = 0, hc = 0; + for (Node<K,V> e = t.first; e != null; e = e.next) { + int h = e.hash; + TreeNode<K,V> p = new TreeNode<K,V> + (h, e.key, e.val, null, null); + if ((h & n) == 0) { + if ((p.prev = loTail) == null) + lo = p; + else + loTail.next = p; + loTail = p; + ++lc; + } + else { + if ((p.prev = hiTail) == null) + hi = p; + else + hiTail.next = p; + hiTail = p; + ++hc; + } + } + ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : + (hc != 0) ? new TreeBin<K,V>(lo) : t; + hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : + (lc != 0) ? new TreeBin<K,V>(hi) : t; + } + else + ln = hn = null; + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + + /* ---------------- Conversion from/to TreeBins -------------- */ + + /** + * Replaces all linked nodes in bin at given index unless table is + * too small, in which case resizes instead. + */ + private final void treeifyBin(Node<K,V>[] tab, int index) { + Node<K,V> b; int n, sc; + if (tab != null) { + if ((n = tab.length) < MIN_TREEIFY_CAPACITY) { + if (tab == table && (sc = sizeCtl) >= 0 && + U.compareAndSwapInt(this, SIZECTL, sc, -2)) + transfer(tab, null); + } + else if ((b = tabAt(tab, index)) != null) { + synchronized (b) { + if (tabAt(tab, index) == b) { + TreeNode<K,V> hd = null, tl = null; + for (Node<K,V> e = b; e != null; e = e.next) { + TreeNode<K,V> p = + new TreeNode<K,V>(e.hash, e.key, e.val, + null, null); + if ((p.prev = tl) == null) + hd = p; + else + tl.next = p; + tl = p; + } + setTabAt(tab, index, new TreeBin<K,V>(hd)); + } + } + } + } + } + + /** + * Returns a list on non-TreeNodes replacing those in given list. + */ + static <K,V> Node<K,V> untreeify(Node<K,V> b) { + Node<K,V> hd = null, tl = null; + for (Node<K,V> q = b; q != null; q = q.next) { + Node<K,V> p = new Node<K,V>(q.hash, q.key, q.val, null); + if (tl == null) + hd = p; + else + tl.next = p; + tl = p; + } + return hd; + } + + /* ---------------- TreeNodes -------------- */ + + /** + * Nodes for use in TreeBins + */ + static final class TreeNode<K,V> extends Node<K,V> { + TreeNode<K,V> parent; // red-black tree links + TreeNode<K,V> left; + TreeNode<K,V> right; + TreeNode<K,V> prev; // needed to unlink next upon deletion + boolean red; + + TreeNode(int hash, K key, V val, Node<K,V> next, + TreeNode<K,V> parent) { + super(hash, key, val, next); + this.parent = parent; + } + + Node<K,V> find(int h, Object k) { + return findTreeNode(h, k, null); } /** - * Sets nextEntry to first node of next non-empty table - * (in backwards order, to simplify checks). + * Returns the TreeNode (or null if not found) for the given key + * starting at given root. */ - final void advance() { - for (;;) { - if (nextTableIndex >= 0) { - if ((nextEntry = entryAt(currentTable, - nextTableIndex--)) != null) + final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) { + if (k != null) { + TreeNode<K,V> p = this; + do { + int ph, dir; K pk; TreeNode<K,V> q; + TreeNode<K,V> pl = p.left, pr = p.right; + if ((ph = p.hash) > h) + p = pl; + else if (ph < h) + p = pr; + else if ((pk = p.key) == k || (pk != null && k.equals(pk))) + return p; + else if (pl == null && pr == null) break; + else if ((kc != null || + (kc = comparableClassFor(k)) != null) && + (dir = compareComparables(kc, k, pk)) != 0) + p = (dir < 0) ? pl : pr; + else if (pl == null) + p = pr; + else if (pr == null || + (q = pr.findTreeNode(h, k, kc)) == null) + p = pl; + else + return q; + } while (p != null); + } + return null; + } + } + + /* ---------------- TreeBins -------------- */ + + /** + * TreeNodes used at the heads of bins. TreeBins do not hold user + * keys or values, but instead point to list of TreeNodes and + * their root. They also maintain a parasitic read-write lock + * forcing writers (who hold bin lock) to wait for readers (who do + * not) to complete before tree restructuring operations. + */ + static final class TreeBin<K,V> extends Node<K,V> { + TreeNode<K,V> root; + volatile TreeNode<K,V> first; + volatile Thread waiter; + volatile int lockState; + // values for lockState + static final int WRITER = 1; // set while holding write lock + static final int WAITER = 2; // set when waiting for write lock + static final int READER = 4; // increment value for setting read lock + + /** + * Creates bin with initial set of nodes headed by b. + */ + TreeBin(TreeNode<K,V> b) { + super(TREEBIN, null, null, null); + this.first = b; + TreeNode<K,V> r = null; + for (TreeNode<K,V> x = b, next; x != null; x = next) { + next = (TreeNode<K,V>)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; } - else if (nextSegmentIndex >= 0) { - Segment<K,V> seg = segmentAt(segments, nextSegmentIndex--); - if (seg != null && (currentTable = seg.table) != null) - nextTableIndex = currentTable.length - 1; + else { + Object key = x.key; + int hash = x.hash; + Class<?> kc = null; + for (TreeNode<K,V> p = r;;) { + int dir, ph; + if ((ph = p.hash) > hash) + dir = -1; + else if (ph < hash) + dir = 1; + else if ((kc != null || + (kc = comparableClassFor(key)) != null)) + dir = compareComparables(kc, key, p.key); + else + dir = 0; + TreeNode<K,V> xp = p; + if ((p = (dir <= 0) ? p.left : p.right) == null) { + x.parent = xp; + if (dir <= 0) + xp.left = x; + else + xp.right = x; + r = balanceInsertion(r, x); + break; + } + } } - else + } + this.root = r; + } + + /** + * Acquires write lock for tree restructuring. + */ + private final void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER)) + contendedLock(); // offload to separate method + } + + /** + * Releases write lock for tree restructuring. + */ + private final void unlockRoot() { + lockState = 0; + } + + /** + * Possibly blocks awaiting root lock. + */ + private final void contendedLock() { + boolean waiting = false; + for (int s;;) { + if (((s = lockState) & WRITER) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) { + if (waiting) + waiter = null; + return; + } + } + else if ((s & WAITER) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) { + waiting = true; + waiter = Thread.currentThread(); + } + } + else if (waiting) + LockSupport.park(this); + } + } + + /** + * Returns matching node or null if none. Tries to search + * using tree comparisons from root, but continues linear + * search when lock not available. + */ + final Node<K,V> find(int h, Object k) { + if (k != null) { + for (Node<K,V> e = first; e != null; e = e.next) { + int s; K ek; + if (((s = lockState) & (WAITER|WRITER)) != 0) { + if (e.hash == h && + ((ek = e.key) == k || (ek != null && k.equals(ek)))) + return e; + } + else if (U.compareAndSwapInt(this, LOCKSTATE, s, + s + READER)) { + TreeNode<K,V> r, p; + try { + p = ((r = root) == null ? null : + r.findTreeNode(h, k, null)); + } finally { + + Thread w; + int ls; + do {} while (!U.compareAndSwapInt + (this, LOCKSTATE, + ls = lockState, ls - READER)); + if (ls == (READER|WAITER) && (w = waiter) != null) + LockSupport.unpark(w); + } + return p; + } + } + } + return null; + } + + /** + * Finds or adds a node. + * @return null if added + */ + final TreeNode<K,V> putTreeVal(int h, K k, V v) { + Class<?> kc = null; + for (TreeNode<K,V> p = root;;) { + int dir, ph; K pk; TreeNode<K,V> q, pr; + if (p == null) { + first = root = new TreeNode<K,V>(h, k, v, null, null); + break; + } + else if ((ph = p.hash) > h) + dir = -1; + else if (ph < h) + dir = 1; + else if ((pk = p.key) == k || (pk != null && k.equals(pk))) + return p; + else if ((kc == null && + (kc = comparableClassFor(k)) == null) || + (dir = compareComparables(kc, k, pk)) == 0) { + if (p.left == null) + dir = 1; + else if ((pr = p.right) == null || + (q = pr.findTreeNode(h, k, kc)) == null) + dir = -1; + else + return q; + } + TreeNode<K,V> xp = p; + if ((p = (dir < 0) ? p.left : p.right) == null) { + TreeNode<K,V> x, f = first; + first = x = new TreeNode<K,V>(h, k, v, f, xp); + if (f != null) + f.prev = x; + if (dir < 0) + xp.left = x; + else + xp.right = x; + if (!xp.red) + x.red = true; + else { + lockRoot(); + try { + root = balanceInsertion(root, x); + } finally { + unlockRoot(); + } + } break; + } } + assert checkInvariants(root); + return null; } - final HashEntry<K,V> nextEntry() { - HashEntry<K,V> e = nextEntry; - if (e == null) - throw new NoSuchElementException(); - lastReturned = e; // cannot assign until after null check - if ((nextEntry = e.next) == null) - advance(); - return e; + /** + * Removes the given node, that must be present before this + * call. This is messier than typical red-black deletion code + * because we cannot swap the contents of an interior node + * with a leaf successor that is pinned by "next" pointers + * that are accessible independently of lock. So instead we + * swap the tree linkages. + * + * @return true if now too small, so should be untreeified + */ + final boolean removeTreeNode(TreeNode<K,V> p) { + TreeNode<K,V> next = (TreeNode<K,V>)p.next; + TreeNode<K,V> pred = p.prev; // unlink traversal pointers + TreeNode<K,V> r, rl; + if (pred == null) + first = next; + else + pred.next = next; + if (next != null) + next.prev = pred; + if (first == null) { + root = null; + return true; + } + if ((r = root) == null || r.right == null || // too small + (rl = r.left) == null || rl.left == null) + return true; + lockRoot(); + try { + TreeNode<K,V> replacement; + TreeNode<K,V> pl = p.left; + TreeNode<K,V> pr = p.right; + if (pl != null && pr != null) { + TreeNode<K,V> s = pr, sl; + while ((sl = s.left) != null) // find successor + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; // swap colors + TreeNode<K,V> sr = s.right; + TreeNode<K,V> pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode<K,V> sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) + sp.left = p; + else + sp.right = p; + } + if ((s.right = pr) != null) + pr.parent = s; + } + p.left = null; + if ((p.right = sr) != null) + sr.parent = p; + if ((s.left = pl) != null) + pl.parent = s; + if ((s.parent = pp) == null) + r = s; + else if (p == pp.left) + pp.left = s; + else + pp.right = s; + if (sr != null) + replacement = sr; + else + replacement = p; + } + else if (pl != null) + replacement = pl; + else if (pr != null) + replacement = pr; + else + replacement = p; + if (replacement != p) { + TreeNode<K,V> pp = replacement.parent = p.parent; + if (pp == null) + r = replacement; + else if (p == pp.left) + pp.left = replacement; + else + pp.right = replacement; + p.left = p.right = p.parent = null; + } + + root = (p.red) ? r : balanceDeletion(r, replacement); + + if (p == replacement) { // detach pointers + TreeNode<K,V> pp; + if ((pp = p.parent) != null) { + if (p == pp.left) + pp.left = null; + else if (p == pp.right) + pp.right = null; + p.parent = null; + } + } + } finally { + unlockRoot(); + } + assert checkInvariants(root); + return false; + } + + /* ------------------------------------------------------------ */ + // Red-black tree methods, all adapted from CLR + + static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, + TreeNode<K,V> p) { + TreeNode<K,V> r, pp, rl; + if (p != null && (r = p.right) != null) { + if ((rl = p.right = r.left) != null) + rl.parent = p; + if ((pp = r.parent = p.parent) == null) + (root = r).red = false; + else if (pp.left == p) + pp.left = r; + else + pp.right = r; + r.left = p; + p.parent = r; + } + return root; + } + + static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, + TreeNode<K,V> p) { + TreeNode<K,V> l, pp, lr; + if (p != null && (l = p.left) != null) { + if ((lr = p.left = l.right) != null) + lr.parent = p; + if ((pp = l.parent = p.parent) == null) + (root = l).red = false; + else if (pp.right == p) + pp.right = l; + else + pp.left = l; + l.right = p; + p.parent = l; + } + return root; } - public final boolean hasNext() { return nextEntry != null; } - public final boolean hasMoreElements() { return nextEntry != null; } + static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, + TreeNode<K,V> x) { + x.red = true; + for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { + if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (!xp.red || (xpp = xp.parent) == null) + return root; + if (xp == (xppl = xpp.left)) { + if ((xppr = xpp.right) != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + root = rotateLeft(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateRight(root, xpp); + } + } + } + } + else { + if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + root = rotateRight(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateLeft(root, xpp); + } + } + } + } + } + } + + static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root, + TreeNode<K,V> x) { + for (TreeNode<K,V> xp, xpl, xpr;;) { + if (x == null || x == root) + return root; + else if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (x.red) { + x.red = false; + return root; + } + else if ((xpl = xp.left) == x) { + if ((xpr = xp.right) != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = rotateLeft(root, xp); + xpr = (xp = x.parent) == null ? null : xp.right; + } + if (xpr == null) + x = xp; + else { + TreeNode<K,V> sl = xpr.left, sr = xpr.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + xpr.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) + sl.red = false; + xpr.red = true; + root = rotateRight(root, xpr); + xpr = (xp = x.parent) == null ? + null : xp.right; + } + if (xpr != null) { + xpr.red = (xp == null) ? false : xp.red; + if ((sr = xpr.right) != null) + sr.red = false; + } + if (xp != null) { + xp.red = false; + root = rotateLeft(root, xp); + } + x = root; + } + } + } + else { // symmetric + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = rotateRight(root, xp); + xpl = (xp = x.parent) == null ? null : xp.left; + } + if (xpl == null) + x = xp; + else { + TreeNode<K,V> sl = xpl.left, sr = xpl.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + xpl.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) + sr.red = false; + xpl.red = true; + root = rotateLeft(root, xpl); + xpl = (xp = x.parent) == null ? + null : xp.left; + } + if (xpl != null) { + xpl.red = (xp == null) ? false : xp.red; + if ((sl = xpl.left) != null) + sl.red = false; + } + if (xp != null) { + xp.red = false; + root = rotateRight(root, xp); + } + x = root; + } + } + } + } + } + + /** + * Recursive invariant check + */ + static <K,V> boolean checkInvariants(TreeNode<K,V> t) { + TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right, + tb = t.prev, tn = (TreeNode<K,V>)t.next; + if (tb != null && tb.next != t) + return false; + if (tn != null && tn.prev != t) + return false; + if (tp != null && t != tp.left && t != tp.right) + return false; + if (tl != null && (tl.parent != t || tl.hash > t.hash)) + return false; + if (tr != null && (tr.parent != t || tr.hash < t.hash)) + return false; + if (t.red && tl != null && tl.red && tr != null && tr.red) + return false; + if (tl != null && !checkInvariants(tl)) + return false; + if (tr != null && !checkInvariants(tr)) + return false; + return true; + } + + private static final sun.misc.Unsafe U; + private static final long LOCKSTATE; + static { + try { + U = sun.misc.Unsafe.getUnsafe(); + Class<?> k = TreeBin.class; + LOCKSTATE = U.objectFieldOffset + (k.getDeclaredField("lockState")); + } catch (Exception e) { + throw new Error(e); + } + } + } + + /* ----------------Table Traversal -------------- */ + + /** + * Encapsulates traversal for methods such as containsValue; also + * serves as a base class for other iterators. + * + * Method advance visits once each still-valid node that was + * reachable upon iterator construction. It might miss some that + * were added to a bin after the bin was visited, which is OK wrt + * consistency guarantees. Maintaining this property in the face + * of possible ongoing resizes requires a fair amount of + * bookkeeping state that is difficult to optimize away amidst + * volatile accesses. Even so, traversal maintains reasonable + * throughput. + * + * Normally, iteration proceeds bin-by-bin traversing lists. + * However, if the table has been resized, then all future steps + * must traverse both the bin at the current index as well as at + * (index + baseSize); and so on for further resizings. To + * paranoically cope with potential sharing by users of iterators + * across threads, iteration terminates if a bounds checks fails + * for a table read. + */ + static class Traverser<K,V> { + Node<K,V>[] tab; // current table; updated if resized + Node<K,V> next; // the next entry to use + int index; // index of bin to use next + int baseIndex; // current index of initial table + int baseLimit; // index bound for initial table + final int baseSize; // initial table size + + Traverser(Node<K,V>[] tab, int size, int index, int limit) { + this.tab = tab; + this.baseSize = size; + this.baseIndex = this.index = index; + this.baseLimit = limit; + this.next = null; + } + + /** + * Advances if possible, returning next valid node, or null if none. + */ + final Node<K,V> advance() { + Node<K,V> e; + if ((e = next) != null) + e = e.next; + for (;;) { + Node<K,V>[] t; int i, n; K ek; // must use locals in checks + if (e != null) + return next = e; + if (baseIndex >= baseLimit || (t = tab) == null || + (n = t.length) <= (i = index) || i < 0) + return next = null; + if ((e = tabAt(t, index)) != null && e.hash < 0) { + if (e instanceof ForwardingNode) { + tab = ((ForwardingNode<K,V>)e).nextTable; + e = null; + continue; + } + else if (e instanceof TreeBin) + e = ((TreeBin<K,V>)e).first; + else + e = null; + } + if ((index += baseSize) >= n) + index = ++baseIndex; // visit upper slots if present + } + } + } + + /** + * Base of key, value, and entry Iterators. Adds fields to + * Traverser to support iterator.remove. + */ + static class BaseIterator<K,V> extends Traverser<K,V> { + final ConcurrentHashMap<K,V> map; + Node<K,V> lastReturned; + BaseIterator(Node<K,V>[] tab, int size, int index, int limit, + ConcurrentHashMap<K,V> map) { + super(tab, size, index, limit); + this.map = map; + advance(); + } + + public final boolean hasNext() { return next != null; } + public final boolean hasMoreElements() { return next != null; } public final void remove() { - if (lastReturned == null) + Node<K,V> p; + if ((p = lastReturned) == null) throw new IllegalStateException(); - ConcurrentHashMap.this.remove(lastReturned.key); lastReturned = null; + map.replaceNode(p.key, null, null); + } + } + + static final class KeyIterator<K,V> extends BaseIterator<K,V> + implements Iterator<K>, Enumeration<K> { + KeyIterator(Node<K,V>[] tab, int index, int size, int limit, + ConcurrentHashMap<K,V> map) { + super(tab, index, size, limit, map); + } + + public final K next() { + Node<K,V> p; + if ((p = next) == null) + throw new NoSuchElementException(); + K k = p.key; + lastReturned = p; + advance(); + return k; } + + public final K nextElement() { return next(); } } - final class KeyIterator - extends HashIterator - implements Iterator<K>, Enumeration<K> - { - public final K next() { return super.nextEntry().key; } - public final K nextElement() { return super.nextEntry().key; } + static final class ValueIterator<K,V> extends BaseIterator<K,V> + implements Iterator<V>, Enumeration<V> { + ValueIterator(Node<K,V>[] tab, int index, int size, int limit, + ConcurrentHashMap<K,V> map) { + super(tab, index, size, limit, map); + } + + public final V next() { + Node<K,V> p; + if ((p = next) == null) + throw new NoSuchElementException(); + V v = p.val; + lastReturned = p; + advance(); + return v; + } + + public final V nextElement() { return next(); } } - final class ValueIterator - extends HashIterator - implements Iterator<V>, Enumeration<V> - { - public final V next() { return super.nextEntry().value; } - public final V nextElement() { return super.nextEntry().value; } + static final class EntryIterator<K,V> extends BaseIterator<K,V> + implements Iterator<Map.Entry<K,V>> { + EntryIterator(Node<K,V>[] tab, int index, int size, int limit, + ConcurrentHashMap<K,V> map) { + super(tab, index, size, limit, map); + } + + public final Map.Entry<K,V> next() { + Node<K,V> p; + if ((p = next) == null) + throw new NoSuchElementException(); + K k = p.key; + V v = p.val; + lastReturned = p; + advance(); + return new MapEntry<K,V>(k, v, map); + } } /** - * Custom Entry class used by EntryIterator.next(), that relays - * setValue changes to the underlying map. + * Exported Entry for EntryIterator */ - final class WriteThroughEntry - extends AbstractMap.SimpleEntry<K,V> - { - WriteThroughEntry(K k, V v) { - super(k,v); + static final class MapEntry<K,V> implements Map.Entry<K,V> { + final K key; // non-null + V val; // non-null + final ConcurrentHashMap<K,V> map; + MapEntry(K key, V val, ConcurrentHashMap<K,V> map) { + this.key = key; + this.val = val; + this.map = map; + } + public K getKey() { return key; } + public V getValue() { return val; } + public int hashCode() { return key.hashCode() ^ val.hashCode(); } + public String toString() { return key + "=" + val; } + + public boolean equals(Object o) { + Object k, v; Map.Entry<?,?> e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry<?,?>)o).getKey()) != null && + (v = e.getValue()) != null && + (k == key || k.equals(key)) && + (v == val || v.equals(val))); } /** * Sets our entry's value and writes through to the map. The - * value to return is somewhat arbitrary here. Since a - * WriteThroughEntry does not necessarily track asynchronous - * changes, the most recent "previous" value could be - * different from what we return (or could even have been - * removed in which case the put will re-establish). We do not - * and cannot guarantee more. + * value to return is somewhat arbitrary here. Since we do not + * necessarily track asynchronous changes, the most recent + * "previous" value could be different from what we return (or + * could even have been removed, in which case the put will + * re-establish). We do not and cannot guarantee more. */ public V setValue(V value) { if (value == null) throw new NullPointerException(); - V v = super.setValue(value); - ConcurrentHashMap.this.put(getKey(), value); + V v = val; + val = value; + map.put(key, value); return v; } } - final class EntryIterator - extends HashIterator - implements Iterator<Entry<K,V>> - { - public Map.Entry<K,V> next() { - HashEntry<K,V> e = super.nextEntry(); - return new WriteThroughEntry(e.key, e.value); + /* ----------------Views -------------- */ + + /** + * Base class for views. + * + */ + abstract static class CollectionView<K,V,E> + implements Collection<E>, java.io.Serializable { + private static final long serialVersionUID = 7249069246763182397L; + final ConcurrentHashMap<K,V> map; + CollectionView(ConcurrentHashMap<K,V> map) { this.map = map; } + + /** + * Returns the map backing this view. + * + * @return the map backing this view + */ + public ConcurrentHashMap<K,V> getMap() { return map; } + + /** + * Removes all of the elements from this view, by removing all + * the mappings from the map backing this view. + */ + public final void clear() { map.clear(); } + public final int size() { return map.size(); } + public final boolean isEmpty() { return map.isEmpty(); } + + // implementations below rely on concrete classes supplying these + // abstract methods + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + */ + public abstract Iterator<E> iterator(); + public abstract boolean contains(Object o); + public abstract boolean remove(Object o); + + private static final String oomeMsg = "Required array size too large"; + + public final Object[] toArray() { + long sz = map.mappingCount(); + if (sz > MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + for (E e : this) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = e; + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + @SuppressWarnings("unchecked") + public final <T> T[] toArray(T[] a) { + long sz = map.mappingCount(); + if (sz > MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + int m = (int)sz; + T[] r = (a.length >= m) ? a : + (T[])java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), m); + int n = r.length; + int i = 0; + for (E e : this) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) + throw new OutOfMemoryError(oomeMsg); + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) + n = MAX_ARRAY_SIZE; + else + n += (n >>> 1) + 1; + r = Arrays.copyOf(r, n); + } + r[i++] = (T)e; + } + if (a == r && i < n) { + r[i] = null; // null-terminate + return r; + } + return (i == n) ? r : Arrays.copyOf(r, i); + } + + /** + * Returns a string representation of this collection. + * The string representation consists of the string representations + * of the collection's elements in the order they are returned by + * its iterator, enclosed in square brackets ({@code "[]"}). + * Adjacent elements are separated by the characters {@code ", "} + * (comma and space). Elements are converted to strings as by + * {@link String#valueOf(Object)}. + * + * @return a string representation of this collection + */ + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator<E> it = iterator(); + if (it.hasNext()) { + for (;;) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) + break; + sb.append(',').append(' '); + } + } + return sb.append(']').toString(); } + + public final boolean containsAll(Collection<?> c) { + if (c != this) { + for (Object e : c) { + if (e == null || !contains(e)) + return false; + } + } + return true; + } + + public final boolean removeAll(Collection<?> c) { + boolean modified = false; + for (Iterator<E> it = iterator(); it.hasNext();) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + public final boolean retainAll(Collection<?> c) { + boolean modified = false; + for (Iterator<E> it = iterator(); it.hasNext();) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + } - final class KeySet extends AbstractSet<K> { - public Iterator<K> iterator() { - return new KeyIterator(); + /** + * A view of a ConcurrentHashMap as a {@link Set} of keys, in + * which additions may optionally be enabled by mapping to a + * common value. This class cannot be directly instantiated. + * See {@link #keySet() keySet()}, + * {@link #keySet(Object) keySet(V)}, + * {@link #newKeySet() newKeySet()}, + * {@link #newKeySet(int) newKeySet(int)}. + * + * @since 1.8 + * + * @hide + */ + public static class KeySetView<K,V> extends CollectionView<K,V,K> + implements Set<K>, java.io.Serializable { + private static final long serialVersionUID = 7249069246763182397L; + private final V value; + KeySetView(ConcurrentHashMap<K,V> map, V value) { // non-public + super(map); + this.value = value; } - public int size() { - return ConcurrentHashMap.this.size(); + + /** + * Returns the default mapped value for additions, + * or {@code null} if additions are not supported. + * + * @return the default mapped value for additions, or {@code null} + * if not supported + */ + public V getMappedValue() { return value; } + + /** + * {@inheritDoc} + * @throws NullPointerException if the specified key is null + */ + public boolean contains(Object o) { return map.containsKey(o); } + + /** + * Removes the key from this map view, by removing the key (and its + * corresponding value) from the backing map. This method does + * nothing if the key is not in the map. + * + * @param o the key to be removed from the backing map + * @return {@code true} if the backing map contained the specified key + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object o) { return map.remove(o) != null; } + + /** + * @return an iterator over the keys of the backing map + */ + public Iterator<K> iterator() { + Node<K,V>[] t; + ConcurrentHashMap<K,V> m = map; + int f = (t = m.table) == null ? 0 : t.length; + return new KeyIterator<K,V>(t, f, 0, f, m); } - public boolean isEmpty() { - return ConcurrentHashMap.this.isEmpty(); + + /** + * Adds the specified key to this set view by mapping the key to + * the default mapped value in the backing map, if defined. + * + * @param e key to be added + * @return {@code true} if this set changed as a result of the call + * @throws NullPointerException if the specified key is null + * @throws UnsupportedOperationException if no default mapped value + * for additions was provided + */ + public boolean add(K e) { + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + return map.putVal(e, v, true) == null; } - public boolean contains(Object o) { - return ConcurrentHashMap.this.containsKey(o); + + /** + * Adds all of the elements in the specified collection to this set, + * as if by calling {@link #add} on each one. + * + * @param c the elements to be inserted into this set + * @return {@code true} if this set changed as a result of the call + * @throws NullPointerException if the collection or any of its + * elements are {@code null} + * @throws UnsupportedOperationException if no default mapped value + * for additions was provided + */ + public boolean addAll(Collection<? extends K> c) { + boolean added = false; + V v; + if ((v = value) == null) + throw new UnsupportedOperationException(); + for (K e : c) { + if (map.putVal(e, v, true) == null) + added = true; + } + return added; } - public boolean remove(Object o) { - return ConcurrentHashMap.this.remove(o) != null; + + public int hashCode() { + int h = 0; + for (K e : this) + h += e.hashCode(); + return h; } - public void clear() { - ConcurrentHashMap.this.clear(); + + public boolean equals(Object o) { + Set<?> c; + return ((o instanceof Set) && + ((c = (Set<?>)o) == this || + (containsAll(c) && c.containsAll(this)))); } + } - final class Values extends AbstractCollection<V> { - public Iterator<V> iterator() { - return new ValueIterator(); + /** + * A view of a ConcurrentHashMap as a {@link Collection} of + * values, in which additions are disabled. This class cannot be + * directly instantiated. See {@link #values()}. + */ + static final class ValuesView<K,V> extends CollectionView<K,V,V> + implements Collection<V>, java.io.Serializable { + private static final long serialVersionUID = 2249069246763182397L; + ValuesView(ConcurrentHashMap<K,V> map) { super(map); } + public final boolean contains(Object o) { + return map.containsValue(o); } - public int size() { - return ConcurrentHashMap.this.size(); + + public final boolean remove(Object o) { + if (o != null) { + for (Iterator<V> it = iterator(); it.hasNext();) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; } - public boolean isEmpty() { - return ConcurrentHashMap.this.isEmpty(); + + public final Iterator<V> iterator() { + ConcurrentHashMap<K,V> m = map; + Node<K,V>[] t; + int f = (t = m.table) == null ? 0 : t.length; + return new ValueIterator<K,V>(t, f, 0, f, m); } - public boolean contains(Object o) { - return ConcurrentHashMap.this.containsValue(o); + + public final boolean add(V e) { + throw new UnsupportedOperationException(); } - public void clear() { - ConcurrentHashMap.this.clear(); + public final boolean addAll(Collection<? extends V> c) { + throw new UnsupportedOperationException(); } + } - final class EntrySet extends AbstractSet<Map.Entry<K,V>> { - public Iterator<Map.Entry<K,V>> iterator() { - return new EntryIterator(); - } + /** + * A view of a ConcurrentHashMap as a {@link Set} of (key, value) + * entries. This class cannot be directly instantiated. See + * {@link #entrySet()}. + */ + static final class EntrySetView<K,V> extends CollectionView<K,V,Map.Entry<K,V>> + implements Set<Map.Entry<K,V>>, java.io.Serializable { + private static final long serialVersionUID = 2249069246763182397L; + EntrySetView(ConcurrentHashMap<K,V> map) { super(map); } + public boolean contains(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry<?,?> e = (Map.Entry<?,?>)o; - V v = ConcurrentHashMap.this.get(e.getKey()); - return v != null && v.equals(e.getValue()); + Object k, v, r; Map.Entry<?,?> e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry<?,?>)o).getKey()) != null && + (r = map.get(k)) != null && + (v = e.getValue()) != null && + (v == r || v.equals(r))); } + public boolean remove(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry<?,?> e = (Map.Entry<?,?>)o; - return ConcurrentHashMap.this.remove(e.getKey(), e.getValue()); + Object k, v; Map.Entry<?,?> e; + return ((o instanceof Map.Entry) && + (k = (e = (Map.Entry<?,?>)o).getKey()) != null && + (v = e.getValue()) != null && + map.remove(k, v)); } - public int size() { - return ConcurrentHashMap.this.size(); + + /** + * @return an iterator over the entries of the backing map + */ + public Iterator<Map.Entry<K,V>> iterator() { + ConcurrentHashMap<K,V> m = map; + Node<K,V>[] t; + int f = (t = m.table) == null ? 0 : t.length; + return new EntryIterator<K,V>(t, f, 0, f, m); } - public boolean isEmpty() { - return ConcurrentHashMap.this.isEmpty(); + + public boolean add(Entry<K,V> e) { + return map.putVal(e.getKey(), e.getValue(), false) == null; } - public void clear() { - ConcurrentHashMap.this.clear(); + + public boolean addAll(Collection<? extends Entry<K,V>> c) { + boolean added = false; + for (Entry<K,V> e : c) { + if (add(e)) + added = true; + } + return added; } + + public final int hashCode() { + int h = 0; + Node<K,V>[] t; + if ((t = map.table) != null) { + Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length); + for (Node<K,V> p; (p = it.advance()) != null; ) { + h += p.hashCode(); + } + } + return h; + } + + public final boolean equals(Object o) { + Set<?> c; + return ((o instanceof Set) && + ((c = (Set<?>)o) == this || + (containsAll(c) && c.containsAll(this)))); + } + } - /* ---------------- Serialization Support -------------- */ + + /* ---------------- Counters -------------- */ + + // Adapted from LongAdder and Striped64. + // See their internal docs for explanation. + + // A padded cell for distributing counts + static final class CounterCell { + volatile long p0, p1, p2, p3, p4, p5, p6; + volatile long value; + volatile long q0, q1, q2, q3, q4, q5, q6; + CounterCell(long x) { value = x; } + } /** - * Saves the state of the <tt>ConcurrentHashMap</tt> instance to a - * stream (i.e., serializes it). - * @param s the stream - * @serialData - * the key (Object) and value (Object) - * for each key-value mapping, followed by a null pair. - * The key-value mappings are emitted in no particular order. + * Holder for the thread-local hash code determining which + * CounterCell to use. The code is initialized via the + * counterHashCodeGenerator, but may be moved upon collisions. */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - // force all segments for serialization compatibility - for (int k = 0; k < segments.length; ++k) - ensureSegment(k); - s.defaultWriteObject(); - - final Segment<K,V>[] segments = this.segments; - for (int k = 0; k < segments.length; ++k) { - Segment<K,V> seg = segmentAt(segments, k); - seg.lock(); - try { - HashEntry<K,V>[] tab = seg.table; - for (int i = 0; i < tab.length; ++i) { - HashEntry<K,V> e; - for (e = entryAt(tab, i); e != null; e = e.next) { - s.writeObject(e.key); - s.writeObject(e.value); - } - } - } finally { - seg.unlock(); - } - } - s.writeObject(null); - s.writeObject(null); + static final class CounterHashCode { + int code; } /** - * Reconstitutes the <tt>ConcurrentHashMap</tt> instance from a - * stream (i.e., deserializes it). - * @param s the stream + * Generates initial value for per-thread CounterHashCodes. */ - @SuppressWarnings("unchecked") - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - s.defaultReadObject(); + static final AtomicInteger counterHashCodeGenerator = new AtomicInteger(); - // Re-initialize segments to be minimally sized, and let grow. - int cap = MIN_SEGMENT_TABLE_CAPACITY; - final Segment<K,V>[] segments = this.segments; - for (int k = 0; k < segments.length; ++k) { - Segment<K,V> seg = segments[k]; - if (seg != null) { - seg.threshold = (int)(cap * seg.loadFactor); - seg.table = (HashEntry<K,V>[]) new HashEntry<?,?>[cap]; + /** + * Increment for counterHashCodeGenerator. See class ThreadLocal + * for explanation. + */ + static final int SEED_INCREMENT = 0x61c88647; + + /** + * Per-thread counter hash codes. Shared across all instances. + */ + static final ThreadLocal<CounterHashCode> threadCounterHashCode = + new ThreadLocal<CounterHashCode>(); + + final long sumCount() { + CounterCell[] as = counterCells; CounterCell a; + long sum = baseCount; + if (as != null) { + for (int i = 0; i < as.length; ++i) { + if ((a = as[i]) != null) + sum += a.value; } } + return sum; + } - // Read the keys and values, and put the mappings in the table + // See LongAdder version for explanation + private final void fullAddCount(long x, CounterHashCode hc, + boolean wasUncontended) { + int h; + if (hc == null) { + hc = new CounterHashCode(); + int s = counterHashCodeGenerator.addAndGet(SEED_INCREMENT); + h = hc.code = (s == 0) ? 1 : s; // Avoid zero + threadCounterHashCode.set(hc); + } + else + h = hc.code; + boolean collide = false; // True if last slot nonempty for (;;) { - K key = (K) s.readObject(); - V value = (V) s.readObject(); - if (key == null) - break; - put(key, value); + CounterCell[] as; CounterCell a; int n; long v; + if ((as = counterCells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (cellsBusy == 0) { // Try to attach new Cell + CounterCell r = new CounterCell(x); // Optimistic create + if (cellsBusy == 0 && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + try { // Recheck under lock + CounterCell[] rs; int m, j; + if ((rs = counterCells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + cellsBusy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) + break; + else if (counterCells != as || n >= NCPU) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (cellsBusy == 0 && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (counterCells == as) {// Expand table unless stale + CounterCell[] rs = new CounterCell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + counterCells = rs; + } + } finally { + cellsBusy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; + } + else if (cellsBusy == 0 && counterCells == as && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + try { // Initialize table + if (counterCells == as) { + CounterCell[] rs = new CounterCell[2]; + rs[h & 1] = new CounterCell(x); + counterCells = rs; + init = true; + } + } finally { + cellsBusy = 0; + } + if (init) + break; + } + else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) + break; // Fall back on using base } + hc.code = h; // Record index for next time } // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long SBASE; - private static final int SSHIFT; - private static final long TBASE; - private static final int TSHIFT; + private static final sun.misc.Unsafe U; + private static final long SIZECTL; + private static final long TRANSFERINDEX; + private static final long TRANSFERORIGIN; + private static final long BASECOUNT; + private static final long CELLSBUSY; + private static final long CELLVALUE; + private static final long ABASE; + private static final int ASHIFT; static { - int ss, ts; try { - UNSAFE = sun.misc.Unsafe.getUnsafe(); - Class<?> tc = HashEntry[].class; - Class<?> sc = Segment[].class; - TBASE = UNSAFE.arrayBaseOffset(tc); - SBASE = UNSAFE.arrayBaseOffset(sc); - ts = UNSAFE.arrayIndexScale(tc); - ss = UNSAFE.arrayIndexScale(sc); + U = sun.misc.Unsafe.getUnsafe(); + Class<?> k = ConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset + (k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset + (k.getDeclaredField("transferIndex")); + TRANSFERORIGIN = U.objectFieldOffset + (k.getDeclaredField("transferOrigin")); + BASECOUNT = U.objectFieldOffset + (k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset + (k.getDeclaredField("cellsBusy")); + Class<?> ck = CounterCell.class; + CELLVALUE = U.objectFieldOffset + (ck.getDeclaredField("value")); + Class<?> ak = Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & (scale - 1)) != 0) + throw new Error("data type scale not a power of two"); + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); } catch (Exception e) { throw new Error(e); } - if ((ss & (ss-1)) != 0 || (ts & (ts-1)) != 0) - throw new Error("data type scale not a power of two"); - SSHIFT = 31 - Integer.numberOfLeadingZeros(ss); - TSHIFT = 31 - Integer.numberOfLeadingZeros(ts); } } diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java index 54b53ae..b38d6a5 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedDeque.java @@ -56,8 +56,6 @@ import java.util.Queue; * actions subsequent to the access or removal of that element from * the {@code ConcurrentLinkedDeque} in another thread. * - * @hide - * * @since 1.7 * @author Doug Lea * @author Martin Buchholz diff --git a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java index 873f825..b39a533 100644 --- a/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java +++ b/luni/src/main/java/java/util/concurrent/ConcurrentLinkedQueue.java @@ -31,7 +31,7 @@ import java.util.Queue; * Like most other concurrent collection implementations, this class * does not permit the use of {@code null} elements. * - * <p>This implementation employs an efficient "wait-free" + * <p>This implementation employs an efficient <em>non-blocking</em> * algorithm based on one described in <a * href="http://www.cs.rochester.edu/u/michael/PODC96.html"> Simple, * Fast, and Practical Non-Blocking and Blocking Concurrent Queue diff --git a/luni/src/main/java/java/util/concurrent/CountedCompleter.java b/luni/src/main/java/java/util/concurrent/CountedCompleter.java index ffe7582..d5f794e 100644 --- a/luni/src/main/java/java/util/concurrent/CountedCompleter.java +++ b/luni/src/main/java/java/util/concurrent/CountedCompleter.java @@ -8,14 +8,15 @@ package java.util.concurrent; /** * A {@link ForkJoinTask} with a completion action performed when - * triggered and there are no remaining pending - * actions. CountedCompleters are in general more robust in the + * triggered and there are no remaining pending actions. + * CountedCompleters are in general more robust in the * presence of subtask stalls and blockage than are other forms of * ForkJoinTasks, but are less intuitive to program. Uses of * CountedCompleter are similar to those of other completion based * components (such as {@link java.nio.channels.CompletionHandler}) * except that multiple <em>pending</em> completions may be necessary - * to trigger the completion action {@link #onCompletion}, not just one. + * to trigger the completion action {@link #onCompletion(CountedCompleter)}, + * not just one. * Unless initialized otherwise, the {@linkplain #getPendingCount pending * count} starts at zero, but may be (atomically) changed using * methods {@link #setPendingCount}, {@link #addToPendingCount}, and @@ -40,9 +41,10 @@ package java.util.concurrent; * <p>A concrete CountedCompleter class must define method {@link * #compute}, that should in most cases (as illustrated below), invoke * {@code tryComplete()} once before returning. The class may also - * optionally override method {@link #onCompletion} to perform an - * action upon normal completion, and method {@link - * #onExceptionalCompletion} to perform an action upon any exception. + * optionally override method {@link #onCompletion(CountedCompleter)} + * to perform an action upon normal completion, and method + * {@link #onExceptionalCompletion(Throwable, CountedCompleter)} to + * perform an action upon any exception. * * <p>CountedCompleters most often do not bear results, in which case * they are normally declared as {@code CountedCompleter<Void>}, and @@ -63,13 +65,14 @@ package java.util.concurrent; * only as an internal helper for other computations, so its own task * status (as reported in methods such as {@link ForkJoinTask#isDone}) * is arbitrary; this status changes only upon explicit invocations of - * {@link #complete}, {@link ForkJoinTask#cancel}, {@link - * ForkJoinTask#completeExceptionally} or upon exceptional completion - * of method {@code compute}. Upon any exceptional completion, the - * exception may be relayed to a task's completer (and its completer, - * and so on), if one exists and it has not otherwise already - * completed. Similarly, cancelling an internal CountedCompleter has - * only a local effect on that completer, so is not often useful. + * {@link #complete}, {@link ForkJoinTask#cancel}, + * {@link ForkJoinTask#completeExceptionally(Throwable)} or upon + * exceptional completion of method {@code compute}. Upon any + * exceptional completion, the exception may be relayed to a task's + * completer (and its completer, and so on), if one exists and it has + * not otherwise already completed. Similarly, cancelling an internal + * CountedCompleter has only a local effect on that completer, so is + * not often useful. * * <p><b>Sample Usages.</b> * @@ -96,8 +99,8 @@ package java.util.concurrent; * improve load balancing. In the recursive case, the second of each * pair of subtasks to finish triggers completion of its parent * (because no result combination is performed, the default no-op - * implementation of method {@code onCompletion} is not overridden). A - * static utility method sets up the base task and invokes it + * implementation of method {@code onCompletion} is not overridden). + * A static utility method sets up the base task and invokes it * (here, implicitly using the {@link ForkJoinPool#commonPool()}). * * <pre> {@code @@ -152,12 +155,11 @@ package java.util.concurrent; * } * }</pre> * - * As a further improvement, notice that the left task need not even - * exist. Instead of creating a new one, we can iterate using the - * original task, and add a pending count for each fork. Additionally, - * because no task in this tree implements an {@link #onCompletion} - * method, {@code tryComplete()} can be replaced with {@link - * #propagateCompletion}. + * As a further improvement, notice that the left task need not even exist. + * Instead of creating a new one, we can iterate using the original task, + * and add a pending count for each fork. Additionally, because no task + * in this tree implements an {@link #onCompletion(CountedCompleter)} method, + * {@code tryComplete()} can be replaced with {@link #propagateCompletion}. * * <pre> {@code * class ForEach<E> ... @@ -235,7 +237,7 @@ package java.util.concurrent; * * <p><b>Recording subtasks.</b> CountedCompleter tasks that combine * results of multiple subtasks usually need to access these results - * in method {@link #onCompletion}. As illustrated in the following + * in method {@link #onCompletion(CountedCompleter)}. As illustrated in the following * class (that performs a simplified form of map-reduce where mappings * and reductions are all of type {@code E}), one way to do this in * divide and conquer designs is to have each subtask record its @@ -357,7 +359,7 @@ package java.util.concurrent; * * <p><b>Triggers.</b> Some CountedCompleters are themselves never * forked, but instead serve as bits of plumbing in other designs; - * including those in which the completion of one of more async tasks + * including those in which the completion of one or more async tasks * triggers another async task. For example: * * <pre> {@code @@ -438,20 +440,21 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> { } /** - * Performs an action when method {@link #completeExceptionally} - * is invoked or method {@link #compute} throws an exception, and - * this task has not otherwise already completed normally. On - * entry to this method, this task {@link - * ForkJoinTask#isCompletedAbnormally}. The return value of this - * method controls further propagation: If {@code true} and this - * task has a completer, then this completer is also completed - * exceptionally. The default implementation of this method does - * nothing except return {@code true}. + * Performs an action when method {@link + * #completeExceptionally(Throwable)} is invoked or method {@link + * #compute} throws an exception, and this task has not already + * otherwise completed normally. On entry to this method, this task + * {@link ForkJoinTask#isCompletedAbnormally}. The return value + * of this method controls further propagation: If {@code true} + * and this task has a completer that has not completed, then that + * completer is also completed exceptionally, with the same + * exception as this completer. The default implementation of + * this method does nothing except return {@code true}. * * @param ex the exception * @param caller the task invoking this method (which may * be this task itself) - * @return true if this exception should be propagated to this + * @return {@code true} if this exception should be propagated to this * task's completer, if one exists */ public boolean onExceptionalCompletion(Throwable ex, CountedCompleter<?> caller) { @@ -492,7 +495,7 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> { * @param delta the value to add */ public final void addToPendingCount(int delta) { - int c; // note: can replace with intrinsic in jdk8 + int c; do {} while (!U.compareAndSwapInt(this, PENDING, c = pending, c+delta)); } @@ -502,7 +505,7 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> { * * @param expected the expected value * @param count the new value - * @return true if successful + * @return {@code true} if successful */ public final boolean compareAndSetPendingCount(int expected, int count) { return U.compareAndSwapInt(this, PENDING, expected, count); @@ -536,9 +539,9 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> { /** * If the pending count is nonzero, decrements the count; - * otherwise invokes {@link #onCompletion} and then similarly - * tries to complete this task's completer, if one exists, - * else marks this task as complete. + * otherwise invokes {@link #onCompletion(CountedCompleter)} + * and then similarly tries to complete this task's completer, + * if one exists, else marks this task as complete. */ public final void tryComplete() { CountedCompleter<?> a = this, s = a; @@ -557,12 +560,12 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> { /** * Equivalent to {@link #tryComplete} but does not invoke {@link - * #onCompletion} along the completion path: If the pending count - * is nonzero, decrements the count; otherwise, similarly tries to - * complete this task's completer, if one exists, else marks this - * task as complete. This method may be useful in cases where - * {@code onCompletion} should not, or need not, be invoked for - * each completer in a computation. + * #onCompletion(CountedCompleter)} along the completion path: + * If the pending count is nonzero, decrements the count; + * otherwise, similarly tries to complete this task's completer, if + * one exists, else marks this task as complete. This method may be + * useful in cases where {@code onCompletion} should not, or need + * not, be invoked for each completer in a computation. */ public final void propagateCompletion() { CountedCompleter<?> a = this, s = a; @@ -579,13 +582,15 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> { } /** - * Regardless of pending count, invokes {@link #onCompletion}, - * marks this task as complete and further triggers {@link - * #tryComplete} on this task's completer, if one exists. The - * given rawResult is used as an argument to {@link #setRawResult} - * before invoking {@link #onCompletion} or marking this task as - * complete; its value is meaningful only for classes overriding - * {@code setRawResult}. + * Regardless of pending count, invokes + * {@link #onCompletion(CountedCompleter)}, marks this task as + * complete and further triggers {@link #tryComplete} on this + * task's completer, if one exists. The given rawResult is + * used as an argument to {@link #setRawResult} before invoking + * {@link #onCompletion(CountedCompleter)} or marking this task + * as complete; its value is meaningful only for classes + * overriding {@code setRawResult}. This method does not modify + * the pending count. * * <p>This method may be useful when forcing completion as soon as * any one (versus all) of several subtask results are obtained. @@ -604,7 +609,6 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> { p.tryComplete(); } - /** * If this task's pending count is zero, returns this task; * otherwise decrements its pending count and returns {@code @@ -668,8 +672,9 @@ public abstract class CountedCompleter<T> extends ForkJoinTask<T> { void internalPropagateException(Throwable ex) { CountedCompleter<?> a = this, s = a; while (a.onExceptionalCompletion(ex, s) && - (a = (s = a).completer) != null && a.status >= 0) - a.recordExceptionalCompletion(ex); + (a = (s = a).completer) != null && a.status >= 0 && + a.recordExceptionalCompletion(ex) == EXCEPTIONAL) + ; } /** diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinPool.java b/luni/src/main/java/java/util/concurrent/ForkJoinPool.java index 87ffff3..9448616 100644 --- a/luni/src/main/java/java/util/concurrent/ForkJoinPool.java +++ b/luni/src/main/java/java/util/concurrent/ForkJoinPool.java @@ -6,6 +6,7 @@ package java.util.concurrent; +import java.lang.Thread.UncaughtExceptionHandler; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -17,6 +18,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; /** @@ -37,7 +39,7 @@ import java.util.concurrent.TimeUnit; * ForkJoinPool}s may also be appropriate for use with event-style * tasks that are never joined. * - * <p>A static {@link #commonPool()} is available and appropriate for + * <p>A static {@code commonPool()} is available and appropriate for * most applications. The common pool is used by any ForkJoinTask that * is not explicitly submitted to a specified pool. Using the common * pool normally reduces resource usage (its threads are slowly @@ -49,9 +51,9 @@ import java.util.concurrent.TimeUnit; * level; by default, equal to the number of available processors. The * pool attempts to maintain enough active (or available) threads by * dynamically adding, suspending, or resuming internal worker - * threads, even if some tasks are stalled waiting to join - * others. However, no such adjustments are guaranteed in the face of - * blocked I/O or other unmanaged synchronization. The nested {@link + * threads, even if some tasks are stalled waiting to join others. + * However, no such adjustments are guaranteed in the face of blocked + * I/O or other unmanaged synchronization. The nested {@link * ManagedBlocker} interface enables extension of the kinds of * synchronization accommodated. * @@ -75,38 +77,45 @@ import java.util.concurrent.TimeUnit; * there is little difference among choice of methods. * * <table BORDER CELLPADDING=3 CELLSPACING=1> + * <caption>Summary of task execution methods</caption> * <tr> * <td></td> * <td ALIGN=CENTER> <b>Call from non-fork/join clients</b></td> * <td ALIGN=CENTER> <b>Call from within fork/join computations</b></td> * </tr> * <tr> - * <td> <b>Arrange async execution</td> + * <td> <b>Arrange async execution</b></td> * <td> {@link #execute(ForkJoinTask)}</td> * <td> {@link ForkJoinTask#fork}</td> * </tr> * <tr> - * <td> <b>Await and obtain result</td> + * <td> <b>Await and obtain result</b></td> * <td> {@link #invoke(ForkJoinTask)}</td> * <td> {@link ForkJoinTask#invoke}</td> * </tr> * <tr> - * <td> <b>Arrange exec and obtain Future</td> + * <td> <b>Arrange exec and obtain Future</b></td> * <td> {@link #submit(ForkJoinTask)}</td> * <td> {@link ForkJoinTask#fork} (ForkJoinTasks <em>are</em> Futures)</td> * </tr> * </table> * * <p>The common pool is by default constructed with default - * parameters, but these may be controlled by setting three {@link - * System#getProperty system properties} with prefix {@code - * java.util.concurrent.ForkJoinPool.common}: {@code parallelism} -- - * an integer greater than zero, {@code threadFactory} -- the class - * name of a {@link ForkJoinWorkerThreadFactory}, and {@code - * exceptionHandler} -- the class name of a {@link - * java.lang.Thread.UncaughtExceptionHandler - * Thread.UncaughtExceptionHandler}. Upon any error in establishing - * these settings, default parameters are used. + * parameters, but these may be controlled by setting three + * {@linkplain System#getProperty system properties}: + * <ul> + * <li>{@code java.util.concurrent.ForkJoinPool.common.parallelism} + * - the parallelism level, a non-negative integer + * <li>{@code java.util.concurrent.ForkJoinPool.common.threadFactory} + * - the class name of a {@link ForkJoinWorkerThreadFactory} + * <li>{@code java.util.concurrent.ForkJoinPool.common.exceptionHandler} + * - the class name of a {@link UncaughtExceptionHandler} + * </ul> + * The system class loader is used to load these classes. + * Upon any error in establishing these settings, default parameters + * are used. It is possible to disable or limit the use of threads in + * the common pool by setting the parallelism property to zero, and/or + * using a factory that may return {@code null}. * * <p><b>Implementation notes</b>: This implementation restricts the * maximum number of running threads to 32767. Attempts to create @@ -118,7 +127,6 @@ import java.util.concurrent.TimeUnit; * or internal resources have been exhausted. * * @since 1.7 - * @hide * @author Doug Lea */ public class ForkJoinPool extends AbstractExecutorService { @@ -153,32 +161,35 @@ public class ForkJoinPool extends AbstractExecutorService { * (http://research.sun.com/scalable/pubs/index.html) and * "Idempotent work stealing" by Michael, Saraswat, and Vechev, * PPoPP 2009 (http://portal.acm.org/citation.cfm?id=1504186). - * The main differences ultimately stem from GC requirements that - * we null out taken slots as soon as we can, to maintain as small - * a footprint as possible even in programs generating huge - * numbers of tasks. To accomplish this, we shift the CAS - * arbitrating pop vs poll (steal) from being on the indices - * ("base" and "top") to the slots themselves. So, both a - * successful pop and poll mainly entail a CAS of a slot from - * non-null to null. Because we rely on CASes of references, we - * do not need tag bits on base or top. They are simple ints as - * used in any circular array-based queue (see for example - * ArrayDeque). Updates to the indices must still be ordered in a - * way that guarantees that top == base means the queue is empty, - * but otherwise may err on the side of possibly making the queue - * appear nonempty when a push, pop, or poll have not fully - * committed. Note that this means that the poll operation, - * considered individually, is not wait-free. One thief cannot - * successfully continue until another in-progress one (or, if - * previously empty, a push) completes. However, in the - * aggregate, we ensure at least probabilistic non-blockingness. - * If an attempted steal fails, a thief always chooses a different - * random victim target to try next. So, in order for one thief to - * progress, it suffices for any in-progress poll or new push on - * any empty queue to complete. (This is why we normally use - * method pollAt and its variants that try once at the apparent - * base index, else consider alternative actions, rather than - * method poll.) + * See also "Correct and Efficient Work-Stealing for Weak Memory + * Models" by Le, Pop, Cohen, and Nardelli, PPoPP 2013 + * (http://www.di.ens.fr/~zappa/readings/ppopp13.pdf) for an + * analysis of memory ordering (atomic, volatile etc) issues. The + * main differences ultimately stem from GC requirements that we + * null out taken slots as soon as we can, to maintain as small a + * footprint as possible even in programs generating huge numbers + * of tasks. To accomplish this, we shift the CAS arbitrating pop + * vs poll (steal) from being on the indices ("base" and "top") to + * the slots themselves. So, both a successful pop and poll + * mainly entail a CAS of a slot from non-null to null. Because + * we rely on CASes of references, we do not need tag bits on base + * or top. They are simple ints as used in any circular + * array-based queue (see for example ArrayDeque). Updates to the + * indices must still be ordered in a way that guarantees that top + * == base means the queue is empty, but otherwise may err on the + * side of possibly making the queue appear nonempty when a push, + * pop, or poll have not fully committed. Note that this means + * that the poll operation, considered individually, is not + * wait-free. One thief cannot successfully continue until another + * in-progress one (or, if previously empty, a push) completes. + * However, in the aggregate, we ensure at least probabilistic + * non-blockingness. If an attempted steal fails, a thief always + * chooses a different random victim target to try next. So, in + * order for one thief to progress, it suffices for any + * in-progress poll or new push on any empty queue to + * complete. (This is why we normally use method pollAt and its + * variants that try once at the apparent base index, else + * consider alternative actions, rather than method poll.) * * This approach also enables support of a user mode in which local * task processing is in FIFO, not LIFO order, simply by using @@ -197,18 +208,17 @@ public class ForkJoinPool extends AbstractExecutorService { * for work-stealing (this would contaminate lifo/fifo * processing). Instead, we randomly associate submission queues * with submitting threads, using a form of hashing. The - * ThreadLocal Submitter class contains a value initially used as - * a hash code for choosing existing queues, but may be randomly - * repositioned upon contention with other submitters. In - * essence, submitters act like workers except that they are - * restricted to executing local tasks that they submitted (or in - * the case of CountedCompleters, others with the same root task). - * However, because most shared/external queue operations are more - * expensive than internal, and because, at steady state, external - * submitters will compete for CPU with workers, ForkJoinTask.join - * and related methods disable them from repeatedly helping to - * process tasks if all workers are active. Insertion of tasks in - * shared mode requires a lock (mainly to protect in the case of + * Submitter probe value serves as a hash code for + * choosing existing queues, and may be randomly repositioned upon + * contention with other submitters. In essence, submitters act + * like workers except that they are restricted to executing local + * tasks that they submitted. However, because most + * shared/external queue operations are more expensive than + * internal, and because, at steady state, external submitters + * will compete for CPU with workers, ForkJoinTask.join and + * related methods disable them from repeatedly helping to process + * tasks if all workers are active. Insertion of tasks in shared + * mode requires a lock (mainly to protect in the case of * resizing) but we use only a simple spinlock (using bits in * field qlock), because submitters encountering a busy queue move * on to try or create other queues -- they block only when @@ -298,37 +308,35 @@ public class ForkJoinPool extends AbstractExecutorService { * has not yet entered the wait queue. We solve this by requiring * a full sweep of all workers (via repeated calls to method * scan()) both before and after a newly waiting worker is added - * to the wait queue. During a rescan, the worker might release - * some other queued worker rather than itself, which has the same - * net effect. Because enqueued workers may actually be rescanning - * rather than waiting, we set and clear the "parker" field of - * WorkQueues to reduce unnecessary calls to unpark. (This - * requires a secondary recheck to avoid missed signals.) Note - * the unusual conventions about Thread.interrupts surrounding - * parking and other blocking: Because interrupts are used solely - * to alert threads to check termination, which is checked anyway - * upon blocking, we clear status (using Thread.interrupted) - * before any call to park, so that park does not immediately - * return due to status being set via some other unrelated call to - * interrupt in user code. + * to the wait queue. Because enqueued workers may actually be + * rescanning rather than waiting, we set and clear the "parker" + * field of WorkQueues to reduce unnecessary calls to unpark. + * (This requires a secondary recheck to avoid missed signals.) + * Note the unusual conventions about Thread.interrupts + * surrounding parking and other blocking: Because interrupts are + * used solely to alert threads to check termination, which is + * checked anyway upon blocking, we clear status (using + * Thread.interrupted) before any call to park, so that park does + * not immediately return due to status being set via some other + * unrelated call to interrupt in user code. * * Signalling. We create or wake up workers only when there * appears to be at least one task they might be able to find and - * execute. However, many other threads may notice the same task - * and each signal to wake up a thread that might take it. So in - * general, pools will be over-signalled. When a submission is - * added or another worker adds a task to a queue that has fewer - * than two tasks, they signal waiting workers (or trigger - * creation of new ones if fewer than the given parallelism level - * -- signalWork), and may leave a hint to the unparked worker to - * help signal others upon wakeup). These primary signals are - * buttressed by others (see method helpSignal) whenever other - * threads scan for work or do not have a task to process. On - * most platforms, signalling (unpark) overhead time is noticeably + * execute. When a submission is added or another worker adds a + * task to a queue that has fewer than two tasks, they signal + * waiting workers (or trigger creation of new ones if fewer than + * the given parallelism level -- signalWork). These primary + * signals are buttressed by others whenever other threads remove + * a task from a queue and notice that there are other tasks there + * as well. So in general, pools will be over-signalled. On most + * platforms, signalling (unpark) overhead time is noticeably * long, and the time between signalling a thread and it actually * making progress can be very noticeably long, so it is worth * offloading these delays from critical paths as much as - * possible. + * possible. Additionally, workers spin-down gradually, by staying + * alive so long as they see the ctl state changing. Similar + * stability-sensing techniques are also used before blocking in + * awaitJoin and helpComplete. * * Trimming workers. To release resources after periods of lack of * use, a worker starting to wait when the pool is quiescent will @@ -409,12 +417,6 @@ public class ForkJoinPool extends AbstractExecutorService { * to find work (see MAX_HELP) and fall back to suspending the * worker and if necessary replacing it with another. * - * Helping actions for CountedCompleters are much simpler: Method - * helpComplete can take and execute any task with the same root - * as the task being waited on. However, this still entails some - * traversal of completer chains, so is less efficient than using - * CountedCompleters without explicit joins. - * * It is impossible to keep exactly the target parallelism number * of threads running at any given time. Determining the * existence of conservatively safe helping targets, the @@ -441,7 +443,7 @@ public class ForkJoinPool extends AbstractExecutorService { * Common Pool * =========== * - * The static commonPool always exists after static + * The static common pool always exists after static * initialization. Since it (or any other created pool) need * never be used, we minimize initial construction overhead and * footprint to the setup of about a dozen fields, with no nested @@ -449,8 +451,11 @@ public class ForkJoinPool extends AbstractExecutorService { * fullExternalPush during the first submission to the pool. * * When external threads submit to the common pool, they can - * perform some subtask processing (see externalHelpJoin and - * related methods). We do not need to record whether these + * perform subtask processing (see externalHelpJoin and related + * methods). This caller-helps policy makes it sensible to set + * common pool parallelism level to one (or more) less than the + * total number of available cores, or even zero for pure + * caller-runs. We do not need to record whether external * submissions are to the common pool -- if not, externalHelpJoin * returns quickly (at the most helping to signal some common pool * workers). These submitters would otherwise be blocked waiting @@ -520,6 +525,7 @@ public class ForkJoinPool extends AbstractExecutorService { * * @param pool the pool this thread works in * @throws NullPointerException if the pool is null + * @return the new worker thread */ public ForkJoinWorkerThread newThread(ForkJoinPool pool); } @@ -536,26 +542,6 @@ public class ForkJoinPool extends AbstractExecutorService { } /** - * Per-thread records for threads that submit to pools. Currently - * holds only pseudo-random seed / index that is used to choose - * submission queues in method externalPush. In the future, this may - * also incorporate a means to implement different task rejection - * and resubmission policies. - * - * Seeds for submitters and workers/workQueues work in basically - * the same way but are initialized and updated using slightly - * different mechanics. Both are initialized using the same - * approach as in class ThreadLocal, where successive values are - * unlikely to collide with previous values. Seeds are then - * randomly modified upon collisions using xorshifts, which - * requires a non-zero seed. - */ - static final class Submitter { - int seed; - Submitter(int s) { seed = s; } - } - - /** * Class for artificial tasks that are used to replace the target * of local joins if they are removed from an interior queue slot * in WorkQueue.tryRemoveAndExec. We don't need the proxy to @@ -614,17 +600,8 @@ public class ForkJoinPool extends AbstractExecutorService { * do not want multiple WorkQueue instances or multiple queue * arrays sharing cache lines. (It would be best for queue objects * and their arrays to share, but there is nothing available to - * help arrange that). Unfortunately, because they are recorded - * in a common array, WorkQueue instances are often moved to be - * adjacent by garbage collectors. To reduce impact, we use field - * padding that works OK on common platforms; this effectively - * trades off slightly slower average field access for the sake of - * avoiding really bad worst-case access. (Until better JVM - * support is in place, this padding is dependent on transient - * properties of JVM field layout rules.) We also take care in - * allocating, sizing and resizing the array. Non-shared queue - * arrays are initialized by workers before use. Others are - * allocated on first use. + * help arrange that). The @Contended annotation alerts JVMs to + * try to keep instances apart. */ static final class WorkQueue { /** @@ -650,13 +627,12 @@ public class ForkJoinPool extends AbstractExecutorService { // Heuristic padding to ameliorate unfortunate memory placements volatile long pad00, pad01, pad02, pad03, pad04, pad05, pad06; - int seed; // for random scanning; initialize nonzero volatile int eventCount; // encoded inactivation count; < 0 if inactive int nextWait; // encoded record of next event waiter - int hint; // steal or signal hint (index) - int poolIndex; // index of this queue in pool (or 0) - final int mode; // 0: lifo, > 0: fifo, < 0: shared int nsteals; // number of steals + int hint; // steal index hint + short poolIndex; // index of this queue in pool + final short mode; // 0: lifo, > 0: fifo, < 0: shared volatile int qlock; // 1: locked, -1: terminate; else 0 volatile int base; // index of next slot for poll int top; // index of next slot for push @@ -674,8 +650,8 @@ public class ForkJoinPool extends AbstractExecutorService { int seed) { this.pool = pool; this.owner = owner; - this.mode = mode; - this.seed = seed; + this.mode = (short)mode; + this.hint = seed; // store initial seed for runWorker // Place indices in the center of array (that is not yet allocated) base = top = INITIAL_QUEUE_CAPACITY >>> 1; } @@ -688,7 +664,7 @@ public class ForkJoinPool extends AbstractExecutorService { return (n >= 0) ? 0 : -n; // ignore transient negative } - /** + /** * Provides a more accurate estimate of whether this queue has * any tasks than does queueSize, by checking whether a * near-empty queue has at least one unclaimed task. @@ -713,20 +689,18 @@ public class ForkJoinPool extends AbstractExecutorService { */ final void push(ForkJoinTask<?> task) { ForkJoinTask<?>[] a; ForkJoinPool p; - int s = top, m, n; + int s = top, n; if ((a = array) != null) { // ignore if queue removed - int j = (((m = a.length - 1) & s) << ASHIFT) + ABASE; - U.putOrderedObject(a, j, task); - if ((n = (top = s + 1) - base) <= 2) { - if ((p = pool) != null) - p.signalWork(this); - } + int m = a.length - 1; + U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task); + if ((n = (top = s + 1) - base) <= 2) + (p = pool).signalWork(p.workQueues, this); else if (n >= m) growArray(); } } - /** + /** * Initializes or doubles the capacity of array. Call either * by owner or with lock held -- it is OK for base, but not * top, to move while resizings are in progress. @@ -784,9 +758,8 @@ public class ForkJoinPool extends AbstractExecutorService { if ((a = array) != null) { int j = (((a.length - 1) & b) << ASHIFT) + ABASE; if ((t = (ForkJoinTask<?>)U.getObjectVolatile(a, j)) != null && - base == b && - U.compareAndSwapObject(a, j, t, null)) { - base = b + 1; + base == b && U.compareAndSwapObject(a, j, t, null)) { + U.putOrderedInt(this, QBASE, b + 1); return t; } } @@ -802,9 +775,8 @@ public class ForkJoinPool extends AbstractExecutorService { int j = (((a.length - 1) & b) << ASHIFT) + ABASE; t = (ForkJoinTask<?>)U.getObjectVolatile(a, j); if (t != null) { - if (base == b && - U.compareAndSwapObject(a, j, t, null)) { - base = b + 1; + if (U.compareAndSwapObject(a, j, t, null)) { + U.putOrderedInt(this, QBASE, b + 1); return t; } } @@ -861,46 +833,43 @@ public class ForkJoinPool extends AbstractExecutorService { ForkJoinTask.cancelIgnoringExceptions(t); } - /** - * Computes next value for random probes. Scans don't require - * a very high quality generator, but also not a crummy one. - * Marsaglia xor-shift is cheap and works well enough. Note: - * This is manually inlined in its usages in ForkJoinPool to - * avoid writes inside busy scan loops. - */ - final int nextSeed() { - int r = seed; - r ^= r << 13; - r ^= r >>> 17; - return seed = r ^= r << 5; - } - // Specialized execution methods /** - * Pops and runs tasks until empty. + * Polls and runs tasks until empty. */ - private void popAndExecAll() { - // A bit faster than repeated pop calls - ForkJoinTask<?>[] a; int m, s; long j; ForkJoinTask<?> t; - while ((a = array) != null && (m = a.length - 1) >= 0 && - (s = top - 1) - base >= 0 && - (t = ((ForkJoinTask<?>) - U.getObject(a, j = ((m & s) << ASHIFT) + ABASE))) - != null) { - if (U.compareAndSwapObject(a, j, t, null)) { - top = s; - t.doExec(); - } - } + final void pollAndExecAll() { + for (ForkJoinTask<?> t; (t = poll()) != null;) + t.doExec(); } /** - * Polls and runs tasks until empty. + * Executes a top-level task and any local tasks remaining + * after execution. */ - private void pollAndExecAll() { - for (ForkJoinTask<?> t; (t = poll()) != null;) - t.doExec(); + final void runTask(ForkJoinTask<?> task) { + if ((currentSteal = task) != null) { + task.doExec(); + ForkJoinTask<?>[] a = array; + int md = mode; + ++nsteals; + currentSteal = null; + if (md != 0) + pollAndExecAll(); + else if (a != null) { + int s, m = a.length - 1; + while ((s = top - 1) - base >= 0) { + long i = ((m & s) << ASHIFT) + ABASE; + ForkJoinTask<?> t = (ForkJoinTask<?>)U.getObject(a, i); + if (t == null) + break; + if (U.compareAndSwapObject(a, i, t, null)) { + top = s; + t.doExec(); + } + } + } + } } /** @@ -911,13 +880,15 @@ public class ForkJoinPool extends AbstractExecutorService { * @return false if no progress can be made, else true */ final boolean tryRemoveAndExec(ForkJoinTask<?> task) { - boolean stat = true, removed = false, empty = true; + boolean stat; ForkJoinTask<?>[] a; int m, s, b, n; - if ((a = array) != null && (m = a.length - 1) >= 0 && + if (task != null && (a = array) != null && (m = a.length - 1) >= 0 && (n = (s = top) - (b = base)) > 0) { + boolean removed = false, empty = true; + stat = true; for (ForkJoinTask<?> t;;) { // traverse from s to b - int j = ((--s & m) << ASHIFT) + ABASE; - t = (ForkJoinTask<?>)U.getObjectVolatile(a, j); + long j = ((--s & m) << ASHIFT) + ABASE; + t = (ForkJoinTask<?>)U.getObject(a, j); if (t == null) // inconsistent length break; else if (t == task) { @@ -945,68 +916,95 @@ public class ForkJoinPool extends AbstractExecutorService { break; } } + if (removed) + task.doExec(); } - if (removed) - task.doExec(); + else + stat = false; return stat; } /** - * Polls for and executes the given task or any other task in - * its CountedCompleter computation. + * Tries to poll for and execute the given task or any other + * task in its CountedCompleter computation. */ - final boolean pollAndExecCC(ForkJoinTask<?> root) { - ForkJoinTask<?>[] a; int b; Object o; - outer: while ((b = base) - top < 0 && (a = array) != null) { + final boolean pollAndExecCC(CountedCompleter<?> root) { + ForkJoinTask<?>[] a; int b; Object o; CountedCompleter<?> t, r; + if ((b = base) - top < 0 && (a = array) != null) { long j = (((a.length - 1) & b) << ASHIFT) + ABASE; - if ((o = U.getObject(a, j)) == null || - !(o instanceof CountedCompleter)) - break; - for (CountedCompleter<?> t = (CountedCompleter<?>)o, r = t;;) { - if (r == root) { - if (base == b && - U.compareAndSwapObject(a, j, t, null)) { - base = b + 1; - t.doExec(); + if ((o = U.getObjectVolatile(a, j)) == null) + return true; // retry + if (o instanceof CountedCompleter) { + for (t = (CountedCompleter<?>)o, r = t;;) { + if (r == root) { + if (base == b && + U.compareAndSwapObject(a, j, t, null)) { + U.putOrderedInt(this, QBASE, b + 1); + t.doExec(); + } return true; } - else - break; // restart + else if ((r = r.completer) == null) + break; // not part of root computation } - if ((r = r.completer) == null) - break outer; // not part of root computation } } return false; } /** - * Executes a top-level task and any local tasks remaining - * after execution. + * Tries to pop and execute the given task or any other task + * in its CountedCompleter computation. */ - final void runTask(ForkJoinTask<?> t) { - if (t != null) { - (currentSteal = t).doExec(); - currentSteal = null; - ++nsteals; - if (base - top < 0) { // process remaining local tasks - if (mode == 0) - popAndExecAll(); - else - pollAndExecAll(); + final boolean externalPopAndExecCC(CountedCompleter<?> root) { + ForkJoinTask<?>[] a; int s; Object o; CountedCompleter<?> t, r; + if (base - (s = top) < 0 && (a = array) != null) { + long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; + if ((o = U.getObject(a, j)) instanceof CountedCompleter) { + for (t = (CountedCompleter<?>)o, r = t;;) { + if (r == root) { + if (U.compareAndSwapInt(this, QLOCK, 0, 1)) { + if (top == s && array == a && + U.compareAndSwapObject(a, j, t, null)) { + top = s - 1; + qlock = 0; + t.doExec(); + } + else + qlock = 0; + } + return true; + } + else if ((r = r.completer) == null) + break; + } } } + return false; } /** - * Executes a non-top-level (stolen) task. + * Internal version */ - final void runSubtask(ForkJoinTask<?> t) { - if (t != null) { - ForkJoinTask<?> ps = currentSteal; - (currentSteal = t).doExec(); - currentSteal = ps; + final boolean internalPopAndExecCC(CountedCompleter<?> root) { + ForkJoinTask<?>[] a; int s; Object o; CountedCompleter<?> t, r; + if (base - (s = top) < 0 && (a = array) != null) { + long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; + if ((o = U.getObject(a, j)) instanceof CountedCompleter) { + for (t = (CountedCompleter<?>)o, r = t;;) { + if (r == root) { + if (U.compareAndSwapObject(a, j, t, null)) { + top = s - 1; + t.doExec(); + } + return true; + } + else if ((r = r.completer) == null) + break; + } + } } + return false; } /** @@ -1023,6 +1021,7 @@ public class ForkJoinPool extends AbstractExecutorService { // Unsafe mechanics private static final sun.misc.Unsafe U; + private static final long QBASE; private static final long QLOCK; private static final int ABASE; private static final int ASHIFT; @@ -1031,6 +1030,8 @@ public class ForkJoinPool extends AbstractExecutorService { U = sun.misc.Unsafe.getUnsafe(); Class<?> k = WorkQueue.class; Class<?> ak = ForkJoinTask[].class; + QBASE = U.objectFieldOffset + (k.getDeclaredField("base")); QLOCK = U.objectFieldOffset (k.getDeclaredField("qlock")); ABASE = U.arrayBaseOffset(ak); @@ -1047,13 +1048,6 @@ public class ForkJoinPool extends AbstractExecutorService { // static fields (initialized in static initializer below) /** - * Creates a new ForkJoinWorkerThread. This factory is used unless - * overridden in ForkJoinPool constructors. - */ - public static final ForkJoinWorkerThreadFactory - defaultForkJoinWorkerThreadFactory; - - /** * Per-thread submission bookkeeping. Shared across all pools * to reduce ThreadLocal pollution and because random motion * to avoid contention in one pool is likely to hold for others. @@ -1063,6 +1057,13 @@ public class ForkJoinPool extends AbstractExecutorService { static final ThreadLocal<Submitter> submitters; /** + * Creates a new ForkJoinWorkerThread. This factory is used unless + * overridden in ForkJoinPool constructors. + */ + public static final ForkJoinWorkerThreadFactory + defaultForkJoinWorkerThreadFactory; + + /** * Permission required for callers of methods that may start or * kill threads. */ @@ -1074,12 +1075,15 @@ public class ForkJoinPool extends AbstractExecutorService { * to paranoically avoid potential initialization circularities * as well as to simplify generated code. */ - static final ForkJoinPool commonPool; + static final ForkJoinPool common; /** - * Common pool parallelism. Must equal commonPool.parallelism. + * Common pool parallelism. To allow simpler use and management + * when common pool threads are disabled, we allow the underlying + * common.parallelism field to be zero, but in that case still report + * parallelism as 1 to reflect resulting caller-runs mechanics. */ - static final int commonPoolParallelism; + static final int commonParallelism; /** * Sequence number for creating workerNamePrefix. @@ -1114,7 +1118,7 @@ public class ForkJoinPool extends AbstractExecutorService { /** * Tolerance for idle timeouts, to cope with timer undershoots */ - private static final long TIMEOUT_SLOP = 2000000L; // 20ms + private static final long TIMEOUT_SLOP = 2000000L; /** * The maximum stolen->joining link depth allowed in method @@ -1216,30 +1220,19 @@ public class ForkJoinPool extends AbstractExecutorService { static final int FIFO_QUEUE = 1; static final int SHARED_QUEUE = -1; - // bounds for #steps in scan loop -- must be power 2 minus 1 - private static final int MIN_SCAN = 0x1ff; // cover estimation slop - private static final int MAX_SCAN = 0x1ffff; // 4 * max workers - - // Instance fields - - /* - * Field layout of this class tends to matter more than one would - * like. Runtime layout order is only loosely related to - * declaration order and may differ across JVMs, but the following - * empirically works OK on current JVMs. - */ - // Heuristic padding to ameliorate unfortunate memory placements volatile long pad00, pad01, pad02, pad03, pad04, pad05, pad06; + // Instance fields volatile long stealCount; // collects worker counts volatile long ctl; // main pool control volatile int plock; // shutdown status and seqLock volatile int indexSeed; // worker/submitter index seed - final int config; // mode and parallelism level + final short parallelism; // parallelism level + final short mode; // LIFO/FIFO WorkQueue[] workQueues; // main registry final ForkJoinWorkerThreadFactory factory; - final Thread.UncaughtExceptionHandler ueh; // per-worker UEH + final UncaughtExceptionHandler ueh; // per-worker UEH final String workerNamePrefix; // to create worker name string volatile Object pad10, pad11, pad12, pad13, pad14, pad15, pad16, pad17; @@ -1254,24 +1247,13 @@ public class ForkJoinPool extends AbstractExecutorService { * a more conservative alternative to a pure spinlock. */ private int acquirePlock() { - int spins = PL_SPINS, r = 0, ps, nps; + int spins = PL_SPINS, ps, nps; for (;;) { if (((ps = plock) & PL_LOCK) == 0 && U.compareAndSwapInt(this, PLOCK, ps, nps = ps + PL_LOCK)) return nps; - else if (r == 0) { // randomize spins if possible - Thread t = Thread.currentThread(); WorkQueue w; Submitter z; - if ((t instanceof ForkJoinWorkerThread) && - (w = ((ForkJoinWorkerThread)t).workQueue) != null) - r = w.seed; - else if ((z = submitters.get()) != null) - r = z.seed; - else - r = 1; - } else if (spins >= 0) { - r ^= r << 1; r ^= r >>> 3; r ^= r << 10; // xorshift - if (r >= 0) + if (ThreadLocalRandom.current().nextInt() >= 0) --spins; } else if (U.compareAndSwapInt(this, PLOCK, ps, ps | PL_SIGNAL)) { @@ -1303,48 +1285,15 @@ public class ForkJoinPool extends AbstractExecutorService { } /** - * Performs secondary initialization, called when plock is zero. - * Creates workQueue array and sets plock to a valid value. The - * lock body must be exception-free (so no try/finally) so we - * optimistically allocate new array outside the lock and throw - * away if (very rarely) not needed. (A similar tactic is used in - * fullExternalPush.) Because the plock seq value can eventually - * wrap around zero, this method harmlessly fails to reinitialize - * if workQueues exists, while still advancing plock. - * - * Additionally tries to create the first worker. - */ - private void initWorkers() { - WorkQueue[] ws, nws; int ps; - int p = config & SMASK; // find power of two table size - int n = (p > 1) ? p - 1 : 1; // ensure at least 2 slots - n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; - n = (n + 1) << 1; - if ((ws = workQueues) == null || ws.length == 0) - nws = new WorkQueue[n]; - else - nws = null; - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - if (((ws = workQueues) == null || ws.length == 0) && nws != null) - workQueues = nws; - int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); - if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) - releasePlock(nps); - tryAddWorker(); - } - - /** * Tries to create and start one worker if fewer than target * parallelism level exist. Adjusts counts etc on failure. */ private void tryAddWorker() { - long c; int u; + long c; int u, e; while ((u = (int)((c = ctl) >>> 32)) < 0 && - (u & SHORT_SIGN) != 0 && (int)c == 0) { - long nc = (long)(((u + UTC_UNIT) & UTC_MASK) | - ((u + UAC_UNIT) & UAC_MASK)) << 32; + (u & SHORT_SIGN) != 0 && (e = (int)c) >= 0) { + long nc = ((long)(((u + UTC_UNIT) & UTC_MASK) | + ((u + UAC_UNIT) & UAC_MASK)) << 32) | (long)e; if (U.compareAndSwapLong(this, CTL, c, nc)) { ForkJoinWorkerThreadFactory fac; Throwable ex = null; @@ -1355,8 +1304,8 @@ public class ForkJoinPool extends AbstractExecutorService { wt.start(); break; } - } catch (Throwable e) { - ex = e; + } catch (Throwable rex) { + ex = rex; } deregisterWorker(wt, ex); break; @@ -1377,14 +1326,14 @@ public class ForkJoinPool extends AbstractExecutorService { * @return the worker's queue */ final WorkQueue registerWorker(ForkJoinWorkerThread wt) { - Thread.UncaughtExceptionHandler handler; WorkQueue[] ws; int s, ps; + UncaughtExceptionHandler handler; WorkQueue[] ws; int s, ps; wt.setDaemon(true); if ((handler = ueh) != null) wt.setUncaughtExceptionHandler(handler); do {} while (!U.compareAndSwapInt(this, INDEXSEED, s = indexSeed, s += SEED_INCREMENT) || s == 0); // skip 0 - WorkQueue w = new WorkQueue(this, wt, config >>> 16, s); + WorkQueue w = new WorkQueue(this, wt, mode, s); if (((ps = plock) & PL_LOCK) != 0 || !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) ps = acquirePlock(); @@ -1404,14 +1353,15 @@ public class ForkJoinPool extends AbstractExecutorService { } } } - w.eventCount = w.poolIndex = r; // volatile write orders + w.poolIndex = (short)r; + w.eventCount = r; // volatile write orders ws[r] = w; } } finally { if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) releasePlock(nps); } - wt.setName(workerNamePrefix.concat(Integer.toString(w.poolIndex))); + wt.setName(workerNamePrefix.concat(Integer.toString(w.poolIndex >>> 1))); return w; } @@ -1421,17 +1371,17 @@ public class ForkJoinPool extends AbstractExecutorService { * array, and adjusts counts. If pool is shutting down, tries to * complete termination. * - * @param wt the worker thread or null if construction failed + * @param wt the worker thread, or null if construction failed * @param ex the exception causing failure, or null if none */ final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) { WorkQueue w = null; if (wt != null && (w = wt.workQueue) != null) { - int ps; + int ps; long sc; w.qlock = -1; // ensure set - long ns = w.nsteals, sc; // collect steal count do {} while (!U.compareAndSwapLong(this, STEALCOUNT, - sc = stealCount, sc + ns)); + sc = stealCount, + sc + w.nsteals)); if (((ps = plock) & PL_LOCK) != 0 || !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) ps = acquirePlock(); @@ -1460,7 +1410,7 @@ public class ForkJoinPool extends AbstractExecutorService { if (e > 0) { // activate or create replacement if ((ws = workQueues) == null || (i = e & SMASK) >= ws.length || - (v = ws[i]) != null) + (v = ws[i]) == null) break; long nc = (((long)(v.nextWait & E_MASK)) | ((long)(u + UAC_UNIT) << 32)); @@ -1489,6 +1439,26 @@ public class ForkJoinPool extends AbstractExecutorService { // Submissions /** + * Per-thread records for threads that submit to pools. Currently + * holds only pseudo-random seed / index that is used to choose + * submission queues in method externalPush. In the future, this may + * also incorporate a means to implement different task rejection + * and resubmission policies. + * + * Seeds for submitters and workers/workQueues work in basically + * the same way but are initialized and updated using slightly + * different mechanics. Both are initialized using the same + * approach as in class ThreadLocal, where successive values are + * unlikely to collide with previous values. Seeds are then + * randomly modified upon collisions using xorshifts, which + * requires a non-zero seed. + */ + static final class Submitter { + int seed; + Submitter(int s) { seed = s; } + } + + /** * Unless shutting down, adds the given task to a submission queue * at submitter's current queue index (modulo submission * range). Only the most common path is directly handled in this @@ -1497,19 +1467,21 @@ public class ForkJoinPool extends AbstractExecutorService { * @param task the task. Caller must ensure non-null. */ final void externalPush(ForkJoinTask<?> task) { - WorkQueue[] ws; WorkQueue q; Submitter z; int m; ForkJoinTask<?>[] a; - if ((z = submitters.get()) != null && plock > 0 && - (ws = workQueues) != null && (m = (ws.length - 1)) >= 0 && - (q = ws[m & z.seed & SQMASK]) != null && + Submitter z = submitters.get(); + WorkQueue q; int r, m, s, n, am; ForkJoinTask<?>[] a; + int ps = plock; + WorkQueue[] ws = workQueues; + if (z != null && ps > 0 && ws != null && (m = (ws.length - 1)) >= 0 && + (q = ws[m & (r = z.seed) & SQMASK]) != null && r != 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) { // lock - int b = q.base, s = q.top, n, an; - if ((a = q.array) != null && (an = a.length) > (n = s + 1 - b)) { - int j = (((an - 1) & s) << ASHIFT) + ABASE; + if ((a = q.array) != null && + (am = a.length - 1) > (n = (s = q.top) - q.base)) { + int j = ((am & s) << ASHIFT) + ABASE; U.putOrderedObject(a, j, task); q.top = s + 1; // push on to deque q.qlock = 0; - if (n <= 2) - signalWork(q); + if (n <= 1) + signalWork(ws, q); return; } q.qlock = 0; @@ -1520,13 +1492,19 @@ public class ForkJoinPool extends AbstractExecutorService { /** * Full version of externalPush. This method is called, among * other times, upon the first submission of the first task to the - * pool, so must perform secondary initialization (via - * initWorkers). It also detects first submission by an external - * thread by looking up its ThreadLocal, and creates a new shared - * queue if the one at index if empty or contended. The plock lock - * body must be exception-free (so no try/finally) so we - * optimistically allocate new queues outside the lock and throw - * them away if (very rarely) not needed. + * pool, so must perform secondary initialization. It also + * detects first submission by an external thread by looking up + * its ThreadLocal, and creates a new shared queue if the one at + * index if empty or contended. The plock lock body must be + * exception-free (so no try/finally) so we optimistically + * allocate new queues outside the lock and throw them away if + * (very rarely) not needed. + * + * Secondary initialization occurs when plock is zero, to create + * workQueue array and set plock to a valid value. This lock body + * must also be exception-free. Because the plock seq value can + * eventually wrap around zero, this method harmlessly fails to + * reinitialize if workQueues exists, while still advancing plock. */ private void fullExternalPush(ForkJoinTask<?> task) { int r = 0; // random index seed @@ -1537,17 +1515,31 @@ public class ForkJoinPool extends AbstractExecutorService { r += SEED_INCREMENT) && r != 0) submitters.set(z = new Submitter(r)); } - else if (r == 0) { // move to a different index + else if (r == 0) { // move to a different index r = z.seed; - r ^= r << 13; // same xorshift as WorkQueues + r ^= r << 13; // same xorshift as WorkQueues r ^= r >>> 17; - z.seed = r ^ (r << 5); + z.seed = r ^= (r << 5); } - else if ((ps = plock) < 0) + if ((ps = plock) < 0) throw new RejectedExecutionException(); else if (ps == 0 || (ws = workQueues) == null || - (m = ws.length - 1) < 0) - initWorkers(); + (m = ws.length - 1) < 0) { // initialize workQueues + int p = parallelism; // find power of two table size + int n = (p > 1) ? p - 1 : 1; // ensure at least 2 slots + n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; + n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1; + WorkQueue[] nws = ((ws = workQueues) == null || ws.length == 0 ? + new WorkQueue[n] : null); + if (((ps = plock) & PL_LOCK) != 0 || + !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) + ps = acquirePlock(); + if (((ws = workQueues) == null || ws.length == 0) && nws != null) + workQueues = nws; + int nps = (ps & SHUTDOWN) | ((ps + PL_LOCK) & ~SHUTDOWN); + if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) + releasePlock(nps); + } else if ((q = ws[k = r & m & SQMASK]) != null) { if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) { ForkJoinTask<?>[] a = q.array; @@ -1565,7 +1557,7 @@ public class ForkJoinPool extends AbstractExecutorService { q.qlock = 0; // unlock } if (submitted) { - signalWork(q); + signalWork(ws, q); return; } } @@ -1573,6 +1565,7 @@ public class ForkJoinPool extends AbstractExecutorService { } else if (((ps = plock) & PL_LOCK) == 0) { // create new queue q = new WorkQueue(this, null, SHARED_QUEUE, r); + q.poolIndex = (short)k; if (((ps = plock) & PL_LOCK) != 0 || !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) ps = acquirePlock(); @@ -1583,7 +1576,7 @@ public class ForkJoinPool extends AbstractExecutorService { releasePlock(nps); } else - r = 0; // try elsewhere while lock held + r = 0; } } @@ -1594,41 +1587,42 @@ public class ForkJoinPool extends AbstractExecutorService { */ final void incrementActiveCount() { long c; - do {} while (!U.compareAndSwapLong(this, CTL, c = ctl, c + AC_UNIT)); + do {} while (!U.compareAndSwapLong + (this, CTL, c = ctl, ((c & ~AC_MASK) | + ((c & AC_MASK) + AC_UNIT)))); } /** * Tries to create or activate a worker if too few are active. * - * @param q the (non-null) queue holding tasks to be signalled + * @param ws the worker array to use to find signallees + * @param q if non-null, the queue holding tasks to be processed */ - final void signalWork(WorkQueue q) { - int hint = q.poolIndex; - long c; int e, u, i, n; WorkQueue[] ws; WorkQueue w; Thread p; - while ((u = (int)((c = ctl) >>> 32)) < 0) { - if ((e = (int)c) > 0) { - if ((ws = workQueues) != null && ws.length > (i = e & SMASK) && - (w = ws[i]) != null && w.eventCount == (e | INT_SIGN)) { - long nc = (((long)(w.nextWait & E_MASK)) | - ((long)(u + UAC_UNIT) << 32)); - if (U.compareAndSwapLong(this, CTL, c, nc)) { - w.hint = hint; - w.eventCount = (e + E_SEQ) & E_MASK; - if ((p = w.parker) != null) - U.unpark(p); - break; - } - if (q.top - q.base <= 0) - break; - } - else - break; - } - else { + final void signalWork(WorkQueue[] ws, WorkQueue q) { + for (;;) { + long c; int e, u, i; WorkQueue w; Thread p; + if ((u = (int)((c = ctl) >>> 32)) >= 0) + break; + if ((e = (int)c) <= 0) { if ((short)u < 0) tryAddWorker(); break; } + if (ws == null || ws.length <= (i = e & SMASK) || + (w = ws[i]) == null) + break; + long nc = (((long)(w.nextWait & E_MASK)) | + ((long)(u + UAC_UNIT)) << 32); + int ne = (e + E_SEQ) & E_MASK; + if (w.eventCount == (e | INT_SIGN) && + U.compareAndSwapLong(this, CTL, c, nc)) { + w.eventCount = ne; + if ((p = w.parker) != null) + U.unpark(p); + break; + } + if (q != null && q.base >= q.top) + break; } } @@ -1639,214 +1633,154 @@ public class ForkJoinPool extends AbstractExecutorService { */ final void runWorker(WorkQueue w) { w.growArray(); // allocate queue - do { w.runTask(scan(w)); } while (w.qlock >= 0); + for (int r = w.hint; scan(w, r) == 0; ) { + r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift + } } /** - * Scans for and, if found, returns one task, else possibly + * Scans for and, if found, runs one task, else possibly * inactivates the worker. This method operates on single reads of * volatile state and is designed to be re-invoked continuously, * in part because it returns upon detecting inconsistencies, * contention, or state changes that indicate possible success on * re-invocation. * - * The scan searches for tasks across queues (starting at a random - * index, and relying on registerWorker to irregularly scatter - * them within array to avoid bias), checking each at least twice. - * The scan terminates upon either finding a non-empty queue, or - * completing the sweep. If the worker is not inactivated, it - * takes and returns a task from this queue. Otherwise, if not - * activated, it signals workers (that may include itself) and - * returns so caller can retry. Also returns for true if the - * worker array may have changed during an empty scan. On failure - * to find a task, we take one of the following actions, after - * which the caller will retry calling this method unless - * terminated. - * - * * If pool is terminating, terminate the worker. - * - * * If not already enqueued, try to inactivate and enqueue the - * worker on wait queue. Or, if inactivating has caused the pool - * to be quiescent, relay to idleAwaitWork to possibly shrink - * pool. - * - * * If already enqueued and none of the above apply, possibly - * park awaiting signal, else lingering to help scan and signal. - * - * * If a non-empty queue discovered or left as a hint, - * help wake up other workers before return. + * The scan searches for tasks across queues starting at a random + * index, checking each at least twice. The scan terminates upon + * either finding a non-empty queue, or completing the sweep. If + * the worker is not inactivated, it takes and runs a task from + * this queue. Otherwise, if not activated, it tries to activate + * itself or some other worker by signalling. On failure to find a + * task, returns (for retry) if pool state may have changed during + * an empty scan, or tries to inactivate if active, else possibly + * blocks or terminates via method awaitWork. * * @param w the worker (via its WorkQueue) - * @return a task or null if none found + * @param r a random seed + * @return worker qlock status if would have waited, else 0 */ - private final ForkJoinTask<?> scan(WorkQueue w) { + private final int scan(WorkQueue w, int r) { WorkQueue[] ws; int m; - int ps = plock; // read plock before ws - if (w != null && (ws = workQueues) != null && (m = ws.length - 1) >= 0) { - int ec = w.eventCount; // ec is negative if inactive - int r = w.seed; r ^= r << 13; r ^= r >>> 17; w.seed = r ^= r << 5; - w.hint = -1; // update seed and clear hint - int j = ((m + m + 1) | MIN_SCAN) & MAX_SCAN; - do { - WorkQueue q; ForkJoinTask<?>[] a; int b; - if ((q = ws[(r + j) & m]) != null && (b = q.base) - q.top < 0 && - (a = q.array) != null) { // probably nonempty - int i = (((a.length - 1) & b) << ASHIFT) + ABASE; - ForkJoinTask<?> t = (ForkJoinTask<?>) - U.getObjectVolatile(a, i); - if (q.base == b && ec >= 0 && t != null && - U.compareAndSwapObject(a, i, t, null)) { - if ((q.base = b + 1) - q.top < 0) - signalWork(q); - return t; // taken - } - else if ((ec < 0 || j < m) && (int)(ctl >> AC_SHIFT) <= 0) { - w.hint = (r + j) & m; // help signal below - break; // cannot take + long c = ctl; // for consistency check + if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && w != null) { + for (int j = m + m + 1, ec = w.eventCount;;) { + WorkQueue q; int b, e; ForkJoinTask<?>[] a; ForkJoinTask<?> t; + if ((q = ws[(r - j) & m]) != null && + (b = q.base) - q.top < 0 && (a = q.array) != null) { + long i = (((a.length - 1) & b) << ASHIFT) + ABASE; + if ((t = ((ForkJoinTask<?>) + U.getObjectVolatile(a, i))) != null) { + if (ec < 0) + helpRelease(c, ws, w, q, b); + else if (q.base == b && + U.compareAndSwapObject(a, i, t, null)) { + U.putOrderedInt(q, QBASE, b + 1); + if ((b + 1) - q.top < 0) + signalWork(ws, q); + w.runTask(t); + } } + break; } - } while (--j >= 0); - - int h, e, ns; long c, sc; WorkQueue q; - if ((ns = w.nsteals) != 0) { - if (U.compareAndSwapLong(this, STEALCOUNT, - sc = stealCount, sc + ns)) - w.nsteals = 0; // collect steals and rescan - } - else if (plock != ps) // consistency check - ; // skip - else if ((e = (int)(c = ctl)) < 0) - w.qlock = -1; // pool is terminating - else { - if ((h = w.hint) < 0) { - if (ec >= 0) { // try to enqueue/inactivate - long nc = (((long)ec | - ((c - AC_UNIT) & (AC_MASK|TC_MASK)))); - w.nextWait = e; // link and mark inactive + else if (--j < 0) { + if ((ec | (e = (int)c)) < 0) // inactive or terminating + return awaitWork(w, c, ec); + else if (ctl == c) { // try to inactivate and enqueue + long nc = (long)ec | ((c - AC_UNIT) & (AC_MASK|TC_MASK)); + w.nextWait = e; w.eventCount = ec | INT_SIGN; - if (ctl != c || !U.compareAndSwapLong(this, CTL, c, nc)) - w.eventCount = ec; // unmark on CAS failure - else if ((int)(c >> AC_SHIFT) == 1 - (config & SMASK)) - idleAwaitWork(w, nc, c); - } - else if (w.eventCount < 0 && !tryTerminate(false, false) && - ctl == c) { // block - Thread wt = Thread.currentThread(); - Thread.interrupted(); // clear status - U.putObject(wt, PARKBLOCKER, this); - w.parker = wt; // emulate LockSupport.park - if (w.eventCount < 0) // recheck - U.park(false, 0L); - w.parker = null; - U.putObject(wt, PARKBLOCKER, null); - } - } - if ((h >= 0 || (h = w.hint) >= 0) && - (ws = workQueues) != null && h < ws.length && - (q = ws[h]) != null) { // signal others before retry - WorkQueue v; Thread p; int u, i, s; - for (int n = (config & SMASK) >>> 1;;) { - int idleCount = (w.eventCount < 0) ? 0 : -1; - if (((s = idleCount - q.base + q.top) <= n && - (n = s) <= 0) || - (u = (int)((c = ctl) >>> 32)) >= 0 || - (e = (int)c) <= 0 || m < (i = e & SMASK) || - (v = ws[i]) == null) - break; - long nc = (((long)(v.nextWait & E_MASK)) | - ((long)(u + UAC_UNIT) << 32)); - if (v.eventCount != (e | INT_SIGN) || - !U.compareAndSwapLong(this, CTL, c, nc)) - break; - v.hint = h; - v.eventCount = (e + E_SEQ) & E_MASK; - if ((p = v.parker) != null) - U.unpark(p); - if (--n <= 0) - break; + if (!U.compareAndSwapLong(this, CTL, c, nc)) + w.eventCount = ec; // back out } + break; } } } - return null; + return 0; } /** - * If inactivating worker w has caused the pool to become - * quiescent, checks for pool termination, and, so long as this is - * not the only worker, waits for event for up to a given - * duration. On timeout, if ctl has not changed, terminates the - * worker, which will in turn wake up another worker to possibly - * repeat this process. + * A continuation of scan(), possibly blocking or terminating + * worker w. Returns without blocking if pool state has apparently + * changed since last invocation. Also, if inactivating w has + * caused the pool to become quiescent, checks for pool + * termination, and, so long as this is not the only worker, waits + * for event for up to a given duration. On timeout, if ctl has + * not changed, terminates the worker, which will in turn wake up + * another worker to possibly repeat this process. * * @param w the calling worker - * @param currentCtl the ctl value triggering possible quiescence - * @param prevCtl the ctl value to restore if thread is terminated - */ - private void idleAwaitWork(WorkQueue w, long currentCtl, long prevCtl) { - if (w != null && w.eventCount < 0 && - !tryTerminate(false, false) && (int)prevCtl != 0) { - int dc = -(short)(currentCtl >>> TC_SHIFT); - long parkTime = dc < 0 ? FAST_IDLE_TIMEOUT: (dc + 1) * IDLE_TIMEOUT; - long deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP; - Thread wt = Thread.currentThread(); - while (ctl == currentCtl) { - Thread.interrupted(); // timed variant of version in scan() - U.putObject(wt, PARKBLOCKER, this); - w.parker = wt; - if (ctl == currentCtl) - U.park(false, parkTime); - w.parker = null; - U.putObject(wt, PARKBLOCKER, null); - if (ctl != currentCtl) - break; - if (deadline - System.nanoTime() <= 0L && - U.compareAndSwapLong(this, CTL, currentCtl, prevCtl)) { - w.eventCount = (w.eventCount + E_SEQ) | E_MASK; - w.qlock = -1; // shrink - break; + * @param c the ctl value on entry to scan + * @param ec the worker's eventCount on entry to scan + */ + private final int awaitWork(WorkQueue w, long c, int ec) { + int stat, ns; long parkTime, deadline; + if ((stat = w.qlock) >= 0 && w.eventCount == ec && ctl == c && + !Thread.interrupted()) { + int e = (int)c; + int u = (int)(c >>> 32); + int d = (u >> UAC_SHIFT) + parallelism; // active count + + if (e < 0 || (d <= 0 && tryTerminate(false, false))) + stat = w.qlock = -1; // pool is terminating + else if ((ns = w.nsteals) != 0) { // collect steals and retry + long sc; + w.nsteals = 0; + do {} while (!U.compareAndSwapLong(this, STEALCOUNT, + sc = stealCount, sc + ns)); + } + else { + long pc = ((d > 0 || ec != (e | INT_SIGN)) ? 0L : + ((long)(w.nextWait & E_MASK)) | // ctl to restore + ((long)(u + UAC_UNIT)) << 32); + if (pc != 0L) { // timed wait if last waiter + int dc = -(short)(c >>> TC_SHIFT); + parkTime = (dc < 0 ? FAST_IDLE_TIMEOUT: + (dc + 1) * IDLE_TIMEOUT); + deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP; + } + else + parkTime = deadline = 0L; + if (w.eventCount == ec && ctl == c) { + Thread wt = Thread.currentThread(); + U.putObject(wt, PARKBLOCKER, this); + w.parker = wt; // emulate LockSupport.park + if (w.eventCount == ec && ctl == c) + U.park(false, parkTime); // must recheck before park + w.parker = null; + U.putObject(wt, PARKBLOCKER, null); + if (parkTime != 0L && ctl == c && + deadline - System.nanoTime() <= 0L && + U.compareAndSwapLong(this, CTL, c, pc)) + stat = w.qlock = -1; // shrink pool } } } + return stat; } /** - * Scans through queues looking for work while joining a task; if - * any present, signals. May return early if more signalling is - * detectably unneeded. - * - * @param task return early if done - * @param origin an index to start scan - */ - private void helpSignal(ForkJoinTask<?> task, int origin) { - WorkQueue[] ws; WorkQueue w; Thread p; long c; int m, u, e, i, s; - if (task != null && task.status >= 0 && - (u = (int)(ctl >>> 32)) < 0 && (u >> UAC_SHIFT) < 0 && - (ws = workQueues) != null && (m = ws.length - 1) >= 0) { - outer: for (int k = origin, j = m; j >= 0; --j) { - WorkQueue q = ws[k++ & m]; - for (int n = m;;) { // limit to at most m signals - if (task.status < 0) - break outer; - if (q == null || - ((s = -q.base + q.top) <= n && (n = s) <= 0)) - break; - if ((u = (int)((c = ctl) >>> 32)) >= 0 || - (e = (int)c) <= 0 || m < (i = e & SMASK) || - (w = ws[i]) == null) - break outer; - long nc = (((long)(w.nextWait & E_MASK)) | - ((long)(u + UAC_UNIT) << 32)); - if (w.eventCount != (e | INT_SIGN)) - break outer; - if (U.compareAndSwapLong(this, CTL, c, nc)) { - w.eventCount = (e + E_SEQ) & E_MASK; - if ((p = w.parker) != null) - U.unpark(p); - if (--n <= 0) - break; - } - } + * Possibly releases (signals) a worker. Called only from scan() + * when a worker with apparently inactive status finds a non-empty + * queue. This requires revalidating all of the associated state + * from caller. + */ + private final void helpRelease(long c, WorkQueue[] ws, WorkQueue w, + WorkQueue q, int b) { + WorkQueue v; int e, i; Thread p; + if (w != null && w.eventCount < 0 && (e = (int)c) > 0 && + ws != null && ws.length > (i = e & SMASK) && + (v = ws[i]) != null && ctl == c) { + long nc = (((long)(v.nextWait & E_MASK)) | + ((long)((int)(c >>> 32) + UAC_UNIT)) << 32); + int ne = (e + E_SEQ) & E_MASK; + if (q != null && q.base == b && w.eventCount < 0 && + v.eventCount == (e | INT_SIGN) && + U.compareAndSwapLong(this, CTL, c, nc)) { + v.eventCount = ne; + if ((p = v.parker) != null) + U.unpark(p); } } } @@ -1871,7 +1805,8 @@ public class ForkJoinPool extends AbstractExecutorService { */ private int tryHelpStealer(WorkQueue joiner, ForkJoinTask<?> task) { int stat = 0, steps = 0; // bound to avoid cycles - if (joiner != null && task != null) { // hoist null checks + if (task != null && joiner != null && + joiner.base - joiner.top >= 0) { // hoist checks restart: for (;;) { ForkJoinTask<?> subtask = task; // current target for (WorkQueue j = joiner, v;;) { // v is stealer of subtask @@ -1898,7 +1833,7 @@ public class ForkJoinPool extends AbstractExecutorService { } } for (;;) { // help stealer or descend to its stealer - ForkJoinTask[] a; int b; + ForkJoinTask[] a; int b; if (subtask.status < 0) // surround probes with continue restart; // consistency checks if ((b = v.base) - v.top < 0 && (a = v.array) != null) { @@ -1909,13 +1844,23 @@ public class ForkJoinPool extends AbstractExecutorService { v.currentSteal != subtask) continue restart; // stale stat = 1; // apparent progress - if (t != null && v.base == b && - U.compareAndSwapObject(a, i, t, null)) { - v.base = b + 1; // help stealer - joiner.runSubtask(t); + if (v.base == b) { + if (t == null) + break restart; + if (U.compareAndSwapObject(a, i, t, null)) { + U.putOrderedInt(v, QBASE, b + 1); + ForkJoinTask<?> ps = joiner.currentSteal; + int jt = joiner.top; + do { + joiner.currentSteal = t; + t.doExec(); // clear local tasks too + } while (task.status >= 0 && + joiner.top != jt && + (t = joiner.pop()) != null); + joiner.currentSteal = ps; + break restart; + } } - else if (v.base == b && ++steps == MAX_HELP) - break restart; // v apparently stalled } else { // empty -- try to descend ForkJoinTask<?> next = v.currentJoin; @@ -1942,27 +1887,33 @@ public class ForkJoinPool extends AbstractExecutorService { * and run tasks within the target's computation. * * @param task the task to join - * @param mode if shared, exit upon completing any task - * if all workers are active - */ - private int helpComplete(ForkJoinTask<?> task, int mode) { - WorkQueue[] ws; WorkQueue q; int m, n, s, u; - if (task != null && (ws = workQueues) != null && - (m = ws.length - 1) >= 0) { - for (int j = 1, origin = j;;) { + */ + private int helpComplete(WorkQueue joiner, CountedCompleter<?> task) { + WorkQueue[] ws; int m; + int s = 0; + if ((ws = workQueues) != null && (m = ws.length - 1) >= 0 && + joiner != null && task != null) { + int j = joiner.poolIndex; + int scans = m + m + 1; + long c = 0L; // for stability check + for (int k = scans; ; j += 2) { + WorkQueue q; if ((s = task.status) < 0) - return s; - if ((q = ws[j & m]) != null && q.pollAndExecCC(task)) { - origin = j; - if (mode == SHARED_QUEUE && - ((u = (int)(ctl >>> 32)) >= 0 || (u >> UAC_SHIFT) >= 0)) + break; + else if (joiner.internalPopAndExecCC(task)) + k = scans; + else if ((s = task.status) < 0) + break; + else if ((q = ws[j & m]) != null && q.pollAndExecCC(task)) + k = scans; + else if (--k < 0) { + if (c == (c = ctl)) break; + k = scans; } - else if ((j = (j + 2) & m) == origin) - break; } } - return 0; + return s; } /** @@ -1971,17 +1922,22 @@ public class ForkJoinPool extends AbstractExecutorService { * for blocking. Fails on contention or termination. Otherwise, * adds a new thread if no idle workers are available and pool * may become starved. + * + * @param c the assumed ctl value */ - final boolean tryCompensate() { - int pc = config & SMASK, e, i, tc; long c; - WorkQueue[] ws; WorkQueue w; Thread p; - if ((ws = workQueues) != null && (e = (int)(c = ctl)) >= 0) { - if (e != 0 && (i = e & SMASK) < ws.length && - (w = ws[i]) != null && w.eventCount == (e | INT_SIGN)) { + final boolean tryCompensate(long c) { + WorkQueue[] ws = workQueues; + int pc = parallelism, e = (int)c, m, tc; + if (ws != null && (m = ws.length - 1) >= 0 && e >= 0 && ctl == c) { + WorkQueue w = ws[e & m]; + if (e != 0 && w != null) { + Thread p; long nc = ((long)(w.nextWait & E_MASK) | (c & (AC_MASK|TC_MASK))); - if (U.compareAndSwapLong(this, CTL, c, nc)) { - w.eventCount = (e + E_SEQ) & E_MASK; + int ne = (e + E_SEQ) & E_MASK; + if (w.eventCount == (e | INT_SIGN) && + U.compareAndSwapLong(this, CTL, c, nc)) { + w.eventCount = ne; if ((p = w.parker) != null) U.unpark(p); return true; // replace with idle worker @@ -2024,23 +1980,20 @@ public class ForkJoinPool extends AbstractExecutorService { */ final int awaitJoin(WorkQueue joiner, ForkJoinTask<?> task) { int s = 0; - if (joiner != null && task != null && (s = task.status) >= 0) { + if (task != null && (s = task.status) >= 0 && joiner != null) { ForkJoinTask<?> prevJoin = joiner.currentJoin; joiner.currentJoin = task; - do {} while ((s = task.status) >= 0 && !joiner.isEmpty() && - joiner.tryRemoveAndExec(task)); // process local tasks - if (s >= 0 && (s = task.status) >= 0) { - helpSignal(task, joiner.poolIndex); - if ((s = task.status) >= 0 && - (task instanceof CountedCompleter)) - s = helpComplete(task, LIFO_QUEUE); - } + do {} while (joiner.tryRemoveAndExec(task) && // process local tasks + (s = task.status) >= 0); + if (s >= 0 && (task instanceof CountedCompleter)) + s = helpComplete(joiner, (CountedCompleter<?>)task); + long cc = 0; // for stability checks while (s >= 0 && (s = task.status) >= 0) { - if ((!joiner.isEmpty() || // try helping - (s = tryHelpStealer(joiner, task)) == 0) && + if ((s = tryHelpStealer(joiner, task)) == 0 && (s = task.status) >= 0) { - helpSignal(task, joiner.poolIndex); - if ((s = task.status) >= 0 && tryCompensate()) { + if (!tryCompensate(cc)) + cc = ctl; + else { if (task.trySetSignal() && (s = task.status) >= 0) { synchronized (task) { if (task.status >= 0) { @@ -2053,9 +2006,11 @@ public class ForkJoinPool extends AbstractExecutorService { task.notifyAll(); } } - long c; // re-activate + long c; // reactivate do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, c + AC_UNIT)); + (this, CTL, c = ctl, + ((c & ~AC_MASK) | + ((c & AC_MASK) + AC_UNIT)))); } } } @@ -2077,15 +2032,11 @@ public class ForkJoinPool extends AbstractExecutorService { if (joiner != null && task != null && (s = task.status) >= 0) { ForkJoinTask<?> prevJoin = joiner.currentJoin; joiner.currentJoin = task; - do {} while ((s = task.status) >= 0 && !joiner.isEmpty() && - joiner.tryRemoveAndExec(task)); - if (s >= 0 && (s = task.status) >= 0) { - helpSignal(task, joiner.poolIndex); - if ((s = task.status) >= 0 && - (task instanceof CountedCompleter)) - s = helpComplete(task, LIFO_QUEUE); - } - if (s >= 0 && joiner.isEmpty()) { + do {} while (joiner.tryRemoveAndExec(task) && // process local tasks + (s = task.status) >= 0); + if (s >= 0) { + if (task instanceof CountedCompleter) + helpComplete(joiner, (CountedCompleter<?>)task); do {} while (task.status >= 0 && tryHelpStealer(joiner, task) > 0); } @@ -2095,29 +2046,22 @@ public class ForkJoinPool extends AbstractExecutorService { /** * Returns a (probably) non-empty steal queue, if one is found - * during a random, then cyclic scan, else null. This method must - * be retried by caller if, by the time it tries to use the queue, - * it is empty. - * @param r a (random) seed for scanning - */ - private WorkQueue findNonEmptyStealQueue(int r) { - for (WorkQueue[] ws;;) { - int ps = plock, m, n; - if ((ws = workQueues) == null || (m = ws.length - 1) < 1) - return null; - for (int j = (m + 1) << 2; ;) { - WorkQueue q = ws[(((r + j) << 1) | 1) & m]; - if (q != null && (n = q.base - q.top) < 0) { - if (n < -1) - signalWork(q); - return q; - } - else if (--j < 0) { - if (plock == ps) - return null; - break; + * during a scan, else null. This method must be retried by + * caller if, by the time it tries to use the queue, it is empty. + */ + private WorkQueue findNonEmptyStealQueue() { + int r = ThreadLocalRandom.current().nextInt(); + for (;;) { + int ps = plock, m; WorkQueue[] ws; WorkQueue q; + if ((ws = workQueues) != null && (m = ws.length - 1) >= 0) { + for (int j = (m + 1) << 2; j >= 0; --j) { + if ((q = ws[(((r - j) << 1) | 1) & m]) != null && + q.base - q.top < 0) + return q; } } + if (plock == ps) + return null; } } @@ -2128,38 +2072,36 @@ public class ForkJoinPool extends AbstractExecutorService { * find tasks either. */ final void helpQuiescePool(WorkQueue w) { + ForkJoinTask<?> ps = w.currentSteal; for (boolean active = true;;) { - ForkJoinTask<?> localTask; // exhaust local queue - while ((localTask = w.nextLocalTask()) != null) - localTask.doExec(); - // Similar to loop in scan(), but ignoring submissions - WorkQueue q = findNonEmptyStealQueue(w.nextSeed()); - if (q != null) { - ForkJoinTask<?> t; int b; + long c; WorkQueue q; ForkJoinTask<?> t; int b; + while ((t = w.nextLocalTask()) != null) + t.doExec(); + if ((q = findNonEmptyStealQueue()) != null) { if (!active) { // re-establish active count - long c; active = true; do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, c + AC_UNIT)); + (this, CTL, c = ctl, + ((c & ~AC_MASK) | + ((c & AC_MASK) + AC_UNIT)))); + } + if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) { + (w.currentSteal = t).doExec(); + w.currentSteal = ps; } - if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) - w.runSubtask(t); } - else { - long c; - if (active) { // decrement active count without queuing + else if (active) { // decrement active count without queuing + long nc = ((c = ctl) & ~AC_MASK) | ((c & AC_MASK) - AC_UNIT); + if ((int)(nc >> AC_SHIFT) + parallelism == 0) + break; // bypass decrement-then-increment + if (U.compareAndSwapLong(this, CTL, c, nc)) active = false; - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, c -= AC_UNIT)); - } - else - c = ctl; // re-increment on exit - if ((int)(c >> AC_SHIFT) + (config & SMASK) == 0) { - do {} while (!U.compareAndSwapLong - (this, CTL, c = ctl, c + AC_UNIT)); - break; - } } + else if ((int)((c = ctl) >> AC_SHIFT) + parallelism <= 0 && + U.compareAndSwapLong + (this, CTL, c, ((c & ~AC_MASK) | + ((c & AC_MASK) + AC_UNIT)))) + break; } } @@ -2173,7 +2115,7 @@ public class ForkJoinPool extends AbstractExecutorService { WorkQueue q; int b; if ((t = w.nextLocalTask()) != null) return t; - if ((q = findNonEmptyStealQueue(w.nextSeed())) == null) + if ((q = findNonEmptyStealQueue()) == null) return null; if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null) return t; @@ -2229,7 +2171,7 @@ public class ForkJoinPool extends AbstractExecutorService { static int getSurplusQueuedTaskCount() { Thread t; ForkJoinWorkerThread wt; ForkJoinPool pool; WorkQueue q; if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)) { - int p = (pool = (wt = (ForkJoinWorkerThread)t).pool).config & SMASK; + int p = (pool = (wt = (ForkJoinWorkerThread)t).pool).parallelism; int n = (q = wt.workQueue).top - q.base; int a = (int)(pool.ctl >> AC_SHIFT) + p; return n - (a > (p >>>= 1) ? 0 : @@ -2258,45 +2200,47 @@ public class ForkJoinPool extends AbstractExecutorService { * @return true if now terminating or terminated */ private boolean tryTerminate(boolean now, boolean enable) { - if (this == commonPool) // cannot shut down + int ps; + if (this == common) // cannot shut down return false; + if ((ps = plock) >= 0) { // enable by setting plock + if (!enable) + return false; + if ((ps & PL_LOCK) != 0 || + !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) + ps = acquirePlock(); + int nps = ((ps + PL_LOCK) & ~SHUTDOWN) | SHUTDOWN; + if (!U.compareAndSwapInt(this, PLOCK, ps, nps)) + releasePlock(nps); + } for (long c;;) { - if (((c = ctl) & STOP_BIT) != 0) { // already terminating - if ((short)(c >>> TC_SHIFT) == -(config & SMASK)) { + if (((c = ctl) & STOP_BIT) != 0) { // already terminating + if ((short)(c >>> TC_SHIFT) + parallelism <= 0) { synchronized (this) { - notifyAll(); // signal when 0 workers + notifyAll(); // signal when 0 workers } } return true; } - if (plock >= 0) { // not yet enabled - int ps; - if (!enable) - return false; - if (((ps = plock) & PL_LOCK) != 0 || - !U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK)) - ps = acquirePlock(); - if (!U.compareAndSwapInt(this, PLOCK, ps, SHUTDOWN)) - releasePlock(SHUTDOWN); - } - if (!now) { // check if idle & no tasks - if ((int)(c >> AC_SHIFT) != -(config & SMASK) || - hasQueuedSubmissions()) + if (!now) { // check if idle & no tasks + WorkQueue[] ws; WorkQueue w; + if ((int)(c >> AC_SHIFT) + parallelism > 0) return false; - // Check for unqueued inactive workers. One pass suffices. - WorkQueue[] ws = workQueues; WorkQueue w; - if (ws != null) { - for (int i = 1; i < ws.length; i += 2) { - if ((w = ws[i]) != null && w.eventCount >= 0) + if ((ws = workQueues) != null) { + for (int i = 0; i < ws.length; ++i) { + if ((w = ws[i]) != null && + (!w.isEmpty() || + ((i & 1) != 0 && w.eventCount >= 0))) { + signalWork(ws, w); return false; + } } } } if (U.compareAndSwapLong(this, CTL, c, c | STOP_BIT)) { for (int pass = 0; pass < 3; ++pass) { - WorkQueue[] ws = workQueues; - if (ws != null) { - WorkQueue w; Thread wt; + WorkQueue[] ws; WorkQueue w; Thread wt; + if ((ws = workQueues) != null) { int n = ws.length; for (int i = 0; i < n; ++i) { if ((w = ws[i]) != null) { @@ -2307,7 +2251,7 @@ public class ForkJoinPool extends AbstractExecutorService { if (!wt.isInterrupted()) { try { wt.interrupt(); - } catch (SecurityException ignore) { + } catch (Throwable ignore) { } } U.unpark(wt); @@ -2318,7 +2262,7 @@ public class ForkJoinPool extends AbstractExecutorService { // Wake up workers parked on event queue int i, e; long cc; Thread p; while ((e = (int)(cc = ctl) & E_MASK) != 0 && - (i = e & SMASK) < n && + (i = e & SMASK) < n && i >= 0 && (w = ws[i]) != null) { long nc = ((long)(w.nextWait & E_MASK) | ((cc + AC_UNIT) & AC_MASK) | @@ -2344,9 +2288,9 @@ public class ForkJoinPool extends AbstractExecutorService { * least one task. */ static WorkQueue commonSubmitterQueue() { - ForkJoinPool p; WorkQueue[] ws; int m; Submitter z; + Submitter z; ForkJoinPool p; WorkQueue[] ws; int m, r; return ((z = submitters.get()) != null && - (p = commonPool) != null && + (p = common) != null && (ws = p.workQueues) != null && (m = ws.length - 1) >= 0) ? ws[m & z.seed & SQMASK] : null; @@ -2355,127 +2299,57 @@ public class ForkJoinPool extends AbstractExecutorService { /** * Tries to pop the given task from submitter's queue in common pool. */ - static boolean tryExternalUnpush(ForkJoinTask<?> t) { - ForkJoinPool p; WorkQueue[] ws; WorkQueue q; Submitter z; - ForkJoinTask<?>[] a; int m, s; - if (t != null && - (z = submitters.get()) != null && - (p = commonPool) != null && - (ws = p.workQueues) != null && - (m = ws.length - 1) >= 0 && - (q = ws[m & z.seed & SQMASK]) != null && - (s = q.top) != q.base && - (a = q.array) != null) { + final boolean tryExternalUnpush(ForkJoinTask<?> task) { + WorkQueue joiner; ForkJoinTask<?>[] a; int m, s; + Submitter z = submitters.get(); + WorkQueue[] ws = workQueues; + boolean popped = false; + if (z != null && ws != null && (m = ws.length - 1) >= 0 && + (joiner = ws[z.seed & m & SQMASK]) != null && + joiner.base != (s = joiner.top) && + (a = joiner.array) != null) { long j = (((a.length - 1) & (s - 1)) << ASHIFT) + ABASE; - if (U.getObject(a, j) == t && - U.compareAndSwapInt(q, QLOCK, 0, 1)) { - if (q.array == a && q.top == s && // recheck - U.compareAndSwapObject(a, j, t, null)) { - q.top = s - 1; - q.qlock = 0; - return true; + if (U.getObject(a, j) == task && + U.compareAndSwapInt(joiner, QLOCK, 0, 1)) { + if (joiner.top == s && joiner.array == a && + U.compareAndSwapObject(a, j, task, null)) { + joiner.top = s - 1; + popped = true; } - q.qlock = 0; + joiner.qlock = 0; } } - return false; + return popped; } - /** - * Tries to pop and run local tasks within the same computation - * as the given root. On failure, tries to help complete from - * other queues via helpComplete. - */ - private void externalHelpComplete(WorkQueue q, ForkJoinTask<?> root) { - ForkJoinTask<?>[] a; int m; - if (q != null && (a = q.array) != null && (m = (a.length - 1)) >= 0 && - root != null && root.status >= 0) { - for (;;) { - int s, u; Object o; CountedCompleter<?> task = null; - if ((s = q.top) - q.base > 0) { - long j = ((m & (s - 1)) << ASHIFT) + ABASE; - if ((o = U.getObject(a, j)) != null && - (o instanceof CountedCompleter)) { - CountedCompleter<?> t = (CountedCompleter<?>)o, r = t; - do { - if (r == root) { - if (U.compareAndSwapInt(q, QLOCK, 0, 1)) { - if (q.array == a && q.top == s && - U.compareAndSwapObject(a, j, t, null)) { - q.top = s - 1; - task = t; - } - q.qlock = 0; - } - break; - } - } while ((r = r.completer) != null); - } - } - if (task != null) - task.doExec(); - if (root.status < 0 || - (u = (int)(ctl >>> 32)) >= 0 || (u >> UAC_SHIFT) >= 0) + final int externalHelpComplete(CountedCompleter<?> task) { + WorkQueue joiner; int m, j; + Submitter z = submitters.get(); + WorkQueue[] ws = workQueues; + int s = 0; + if (z != null && ws != null && (m = ws.length - 1) >= 0 && + (joiner = ws[(j = z.seed) & m & SQMASK]) != null && task != null) { + int scans = m + m + 1; + long c = 0L; // for stability check + j |= 1; // poll odd queues + for (int k = scans; ; j += 2) { + WorkQueue q; + if ((s = task.status) < 0) break; - if (task == null) { - helpSignal(root, q.poolIndex); - if (root.status >= 0) - helpComplete(root, SHARED_QUEUE); + else if (joiner.externalPopAndExecCC(task)) + k = scans; + else if ((s = task.status) < 0) break; + else if ((q = ws[j & m]) != null && q.pollAndExecCC(task)) + k = scans; + else if (--k < 0) { + if (c == (c = ctl)) + break; + k = scans; } } } - } - - /** - * Tries to help execute or signal availability of the given task - * from submitter's queue in common pool. - */ - static void externalHelpJoin(ForkJoinTask<?> t) { - // Some hard-to-avoid overlap with tryExternalUnpush - ForkJoinPool p; WorkQueue[] ws; WorkQueue q, w; Submitter z; - ForkJoinTask<?>[] a; int m, s, n; - if (t != null && - (z = submitters.get()) != null && - (p = commonPool) != null && - (ws = p.workQueues) != null && - (m = ws.length - 1) >= 0 && - (q = ws[m & z.seed & SQMASK]) != null && - (a = q.array) != null) { - int am = a.length - 1; - if ((s = q.top) != q.base) { - long j = ((am & (s - 1)) << ASHIFT) + ABASE; - if (U.getObject(a, j) == t && - U.compareAndSwapInt(q, QLOCK, 0, 1)) { - if (q.array == a && q.top == s && - U.compareAndSwapObject(a, j, t, null)) { - q.top = s - 1; - q.qlock = 0; - t.doExec(); - } - else - q.qlock = 0; - } - } - if (t.status >= 0) { - if (t instanceof CountedCompleter) - p.externalHelpComplete(q, t); - else - p.helpSignal(t, q.poolIndex); - } - } - } - - /** - * Restricted version of helpQuiescePool for external callers - */ - static void externalHelpQuiescePool() { - ForkJoinPool p; ForkJoinTask<?> t; WorkQueue q; int b; - if ((p = commonPool) != null && - (q = p.findNonEmptyStealQueue(1)) != null && - (b = q.base) - q.top < 0 && - (t = q.pollAt(b)) != null) - t.doExec(); + return s; } // Exported methods @@ -2529,49 +2403,65 @@ public class ForkJoinPool extends AbstractExecutorService { */ public ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, - Thread.UncaughtExceptionHandler handler, + UncaughtExceptionHandler handler, boolean asyncMode) { + this(checkParallelism(parallelism), + checkFactory(factory), + handler, + (asyncMode ? FIFO_QUEUE : LIFO_QUEUE), + "ForkJoinPool-" + nextPoolId() + "-worker-"); checkPermission(); - if (factory == null) - throw new NullPointerException(); + } + + private static int checkParallelism(int parallelism) { if (parallelism <= 0 || parallelism > MAX_CAP) throw new IllegalArgumentException(); - this.factory = factory; - this.ueh = handler; - this.config = parallelism | (asyncMode ? (FIFO_QUEUE << 16) : 0); - long np = (long)(-parallelism); // offset ctl counts - this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK); - int pn = nextPoolId(); - StringBuilder sb = new StringBuilder("ForkJoinPool-"); - sb.append(Integer.toString(pn)); - sb.append("-worker-"); - this.workerNamePrefix = sb.toString(); + return parallelism; + } + + private static ForkJoinWorkerThreadFactory checkFactory + (ForkJoinWorkerThreadFactory factory) { + if (factory == null) + throw new NullPointerException(); + return factory; } /** - * Constructor for common pool, suitable only for static initialization. - * Basically the same as above, but uses smallest possible initial footprint. + * Creates a {@code ForkJoinPool} with the given parameters, without + * any security checks or parameter validation. Invoked directly by + * makeCommonPool. */ - ForkJoinPool(int parallelism, long ctl, - ForkJoinWorkerThreadFactory factory, - Thread.UncaughtExceptionHandler handler) { - this.config = parallelism; - this.ctl = ctl; + private ForkJoinPool(int parallelism, + ForkJoinWorkerThreadFactory factory, + UncaughtExceptionHandler handler, + int mode, + String workerNamePrefix) { + this.workerNamePrefix = workerNamePrefix; this.factory = factory; this.ueh = handler; - this.workerNamePrefix = "ForkJoinPool.commonPool-worker-"; + this.mode = (short)mode; + this.parallelism = (short)parallelism; + long np = (long)(-parallelism); // offset ctl counts + this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK); } /** - * Returns the common pool instance. + * Returns the common pool instance. This pool is statically + * constructed; its run state is unaffected by attempts to {@link + * #shutdown} or {@link #shutdownNow}. However this pool and any + * ongoing processing are automatically terminated upon program + * {@link System#exit}. Any program that relies on asynchronous + * task processing to complete before program termination should + * invoke {@code commonPool().}{@link #awaitQuiescence awaitQuiescence}, + * before exit. * * @return the common pool instance * @since 1.8 * @hide */ public static ForkJoinPool commonPool() { - // assert commonPool != null : "static init error"; - return commonPool; + // assert common != null : "static init error"; + return common; } // Execution methods @@ -2627,7 +2517,7 @@ public class ForkJoinPool extends AbstractExecutorService { if (task instanceof ForkJoinTask<?>) // avoid re-wrap job = (ForkJoinTask<?>) task; else - job = new ForkJoinTask.AdaptedRunnableAction(task); + job = new ForkJoinTask.RunnableExecuteAction(task); externalPush(job); } @@ -2729,7 +2619,7 @@ public class ForkJoinPool extends AbstractExecutorService { * * @return the handler, or {@code null} if none */ - public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { + public UncaughtExceptionHandler getUncaughtExceptionHandler() { return ueh; } @@ -2739,7 +2629,8 @@ public class ForkJoinPool extends AbstractExecutorService { * @return the targeted parallelism level of this pool */ public int getParallelism() { - return config & SMASK; + int par; + return ((par = parallelism) > 0) ? par : 1; } /** @@ -2750,7 +2641,7 @@ public class ForkJoinPool extends AbstractExecutorService { * @hide */ public static int getCommonPoolParallelism() { - return commonPoolParallelism; + return commonParallelism; } /** @@ -2762,7 +2653,7 @@ public class ForkJoinPool extends AbstractExecutorService { * @return the number of worker threads */ public int getPoolSize() { - return (config & SMASK) + (short)(ctl >>> TC_SHIFT); + return parallelism + (short)(ctl >>> TC_SHIFT); } /** @@ -2772,7 +2663,7 @@ public class ForkJoinPool extends AbstractExecutorService { * @return {@code true} if this pool uses async mode */ public boolean getAsyncMode() { - return (config >>> 16) == FIFO_QUEUE; + return mode == FIFO_QUEUE; } /** @@ -2803,7 +2694,7 @@ public class ForkJoinPool extends AbstractExecutorService { * @return the number of active threads */ public int getActiveThreadCount() { - int r = (config & SMASK) + (int)(ctl >> AC_SHIFT); + int r = parallelism + (int)(ctl >> AC_SHIFT); return (r <= 0) ? 0 : r; // suppress momentarily negative values } @@ -2819,7 +2710,7 @@ public class ForkJoinPool extends AbstractExecutorService { * @return {@code true} if all threads are currently idle */ public boolean isQuiescent() { - return (int)(ctl >> AC_SHIFT) + (config & SMASK) == 0; + return parallelism + (int)(ctl >> AC_SHIFT) <= 0; } /** @@ -2982,7 +2873,7 @@ public class ForkJoinPool extends AbstractExecutorService { } } } - int pc = (config & SMASK); + int pc = parallelism; int tc = pc + (short)(c >>> TC_SHIFT); int ac = pc + (int)(c >> AC_SHIFT); if (ac < 0) // ignore transient negative @@ -3008,15 +2899,10 @@ public class ForkJoinPool extends AbstractExecutorService { * Possibly initiates an orderly shutdown in which previously * submitted tasks are executed, but no new tasks will be * accepted. Invocation has no effect on execution state if this - * is the {@link #commonPool()}, and no additional effect if + * is the {@code commonPool()}, and no additional effect if * already shut down. Tasks that are in the process of being * submitted concurrently during the course of this method may or * may not be rejected. - * - * @throws SecurityException if a security manager exists and - * the caller is not permitted to modify threads - * because it does not hold {@link - * java.lang.RuntimePermission}{@code ("modifyThread")} */ public void shutdown() { checkPermission(); @@ -3026,7 +2912,7 @@ public class ForkJoinPool extends AbstractExecutorService { /** * Possibly attempts to cancel and/or stop all tasks, and reject * all subsequently submitted tasks. Invocation has no effect on - * execution state if this is the {@link #commonPool()}, and no + * execution state if this is the {@code commonPool()}, and no * additional effect if already shut down. Otherwise, tasks that * are in the process of being submitted or executed concurrently * during the course of this method may or may not be @@ -3051,7 +2937,7 @@ public class ForkJoinPool extends AbstractExecutorService { public boolean isTerminated() { long c = ctl; return ((c & STOP_BIT) != 0L && - (short)(c >>> TC_SHIFT) == -(config & SMASK)); + (short)(c >>> TC_SHIFT) + parallelism <= 0); } /** @@ -3070,7 +2956,7 @@ public class ForkJoinPool extends AbstractExecutorService { public boolean isTerminating() { long c = ctl; return ((c & STOP_BIT) != 0L && - (short)(c >>> TC_SHIFT) != -(config & SMASK)); + (short)(c >>> TC_SHIFT) + parallelism > 0); } /** @@ -3085,9 +2971,10 @@ public class ForkJoinPool extends AbstractExecutorService { /** * Blocks until all tasks have completed execution after a * shutdown request, or the timeout occurs, or the current thread - * is interrupted, whichever happens first. Note that the {@link - * #commonPool()} never terminates until program shutdown so - * this method will always time out. + * is interrupted, whichever happens first. Because the {@code + * commonPool()} never terminates until program shutdown, when + * applied to the common pool, this method is equivalent to {@link + * #awaitQuiescence(long, TimeUnit)} but always returns {@code false}. * * @param timeout the maximum time to wait * @param unit the time unit of the timeout argument @@ -3097,6 +2984,12 @@ public class ForkJoinPool extends AbstractExecutorService { */ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + if (this == common) { + awaitQuiescence(timeout, unit); + return false; + } long nanos = unit.toNanos(timeout); if (isTerminated()) return true; @@ -3117,6 +3010,59 @@ public class ForkJoinPool extends AbstractExecutorService { } /** + * If called by a ForkJoinTask operating in this pool, equivalent + * in effect to {@link ForkJoinTask#helpQuiesce}. Otherwise, + * waits and/or attempts to assist performing tasks until this + * pool {@link #isQuiescent} or the indicated timeout elapses. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @return {@code true} if quiescent; {@code false} if the + * timeout elapsed. + */ + public boolean awaitQuiescence(long timeout, TimeUnit unit) { + long nanos = unit.toNanos(timeout); + ForkJoinWorkerThread wt; + Thread thread = Thread.currentThread(); + if ((thread instanceof ForkJoinWorkerThread) && + (wt = (ForkJoinWorkerThread)thread).pool == this) { + helpQuiescePool(wt.workQueue); + return true; + } + long startTime = System.nanoTime(); + WorkQueue[] ws; + int r = 0, m; + boolean found = true; + while (!isQuiescent() && (ws = workQueues) != null && + (m = ws.length - 1) >= 0) { + if (!found) { + if ((System.nanoTime() - startTime) > nanos) + return false; + Thread.yield(); // cannot block + } + found = false; + for (int j = (m + 1) << 2; j >= 0; --j) { + ForkJoinTask<?> t; WorkQueue q; int b; + if ((q = ws[r++ & m]) != null && (b = q.base) - q.top < 0) { + found = true; + if ((t = q.pollAt(b)) != null) + t.doExec(); + break; + } + } + } + return true; + } + + /** + * Waits and/or attempts to assist performing tasks indefinitely + * until the {@code commonPool()} {@link #isQuiescent}. + */ + static void quiesceCommonPool() { + common.awaitQuiescence(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } + + /** * Interface for extending managed parallelism for tasks running * in {@link ForkJoinPool}s. * @@ -3125,9 +3071,9 @@ public class ForkJoinPool extends AbstractExecutorService { * not necessary. Method {@code block} blocks the current thread * if necessary (perhaps internally invoking {@code isReleasable} * before actually blocking). These actions are performed by any - * thread invoking {@link ForkJoinPool#managedBlock}. The - * unusual methods in this API accommodate synchronizers that may, - * but don't usually, block for long periods. Similarly, they + * thread invoking {@link ForkJoinPool#managedBlock(ManagedBlocker)}. + * The unusual methods in this API accommodate synchronizers that + * may, but don't usually, block for long periods. Similarly, they * allow more efficient internal handling of cases in which * additional workers may be, but usually are not, needed to * ensure sufficient parallelism. Toward this end, @@ -3185,6 +3131,7 @@ public class ForkJoinPool extends AbstractExecutorService { /** * Returns {@code true} if blocking is unnecessary. + * @return {@code true} if blocking is unnecessary */ boolean isReleasable(); } @@ -3214,21 +3161,8 @@ public class ForkJoinPool extends AbstractExecutorService { Thread t = Thread.currentThread(); if (t instanceof ForkJoinWorkerThread) { ForkJoinPool p = ((ForkJoinWorkerThread)t).pool; - while (!blocker.isReleasable()) { // variant of helpSignal - WorkQueue[] ws; WorkQueue q; int m, u; - if ((ws = p.workQueues) != null && (m = ws.length - 1) >= 0) { - for (int i = 0; i <= m; ++i) { - if (blocker.isReleasable()) - return; - if ((q = ws[i]) != null && q.base - q.top < 0) { - p.signalWork(q); - if ((u = (int)(p.ctl >>> 32)) >= 0 || - (u >> UAC_SHIFT) >= 0) - break; - } - } - } - if (p.tryCompensate()) { + while (!blocker.isReleasable()) { + if (p.tryCompensate(p.ctl)) { try { do {} while (!blocker.isReleasable() && !blocker.block()); @@ -3266,6 +3200,7 @@ public class ForkJoinPool extends AbstractExecutorService { private static final long STEALCOUNT; private static final long PLOCK; private static final long INDEXSEED; + private static final long QBASE; private static final long QLOCK; static { @@ -3285,6 +3220,8 @@ public class ForkJoinPool extends AbstractExecutorService { PARKBLOCKER = U.objectFieldOffset (tk.getDeclaredField("parkBlocker")); Class<?> wk = WorkQueue.class; + QBASE = U.objectFieldOffset + (wk.getDeclaredField("base")); QLOCK = U.objectFieldOffset (wk.getDeclaredField("qlock")); Class<?> ak = ForkJoinTask[].class; @@ -3298,45 +3235,51 @@ public class ForkJoinPool extends AbstractExecutorService { } submitters = new ThreadLocal<Submitter>(); - ForkJoinWorkerThreadFactory fac = defaultForkJoinWorkerThreadFactory = + defaultForkJoinWorkerThreadFactory = new DefaultForkJoinWorkerThreadFactory(); modifyThreadPermission = new RuntimePermission("modifyThread"); - /* - * Establish common pool parameters. For extra caution, - * computations to set up common pool state are here; the - * constructor just assigns these values to fields. - */ + common = java.security.AccessController.doPrivileged + (new java.security.PrivilegedAction<ForkJoinPool>() { + public ForkJoinPool run() { return makeCommonPool(); }}); + int par = common.parallelism; // report 1 even if threads disabled + commonParallelism = par > 0 ? par : 1; + } - int par = 0; - Thread.UncaughtExceptionHandler handler = null; - try { // TBD: limit or report ignored exceptions? + /** + * Creates and returns the common pool, respecting user settings + * specified via system properties. + */ + private static ForkJoinPool makeCommonPool() { + int parallelism = -1; + ForkJoinWorkerThreadFactory factory + = defaultForkJoinWorkerThreadFactory; + UncaughtExceptionHandler handler = null; + try { // ignore exceptions in accessing/parsing properties String pp = System.getProperty ("java.util.concurrent.ForkJoinPool.common.parallelism"); - String hp = System.getProperty - ("java.util.concurrent.ForkJoinPool.common.exceptionHandler"); String fp = System.getProperty ("java.util.concurrent.ForkJoinPool.common.threadFactory"); + String hp = System.getProperty + ("java.util.concurrent.ForkJoinPool.common.exceptionHandler"); + if (pp != null) + parallelism = Integer.parseInt(pp); if (fp != null) - fac = ((ForkJoinWorkerThreadFactory)ClassLoader. - getSystemClassLoader().loadClass(fp).newInstance()); + factory = ((ForkJoinWorkerThreadFactory)ClassLoader. + getSystemClassLoader().loadClass(fp).newInstance()); if (hp != null) - handler = ((Thread.UncaughtExceptionHandler)ClassLoader. + handler = ((UncaughtExceptionHandler)ClassLoader. getSystemClassLoader().loadClass(hp).newInstance()); - if (pp != null) - par = Integer.parseInt(pp); } catch (Exception ignore) { } - if (par <= 0) - par = Runtime.getRuntime().availableProcessors(); - if (par > MAX_CAP) - par = MAX_CAP; - commonPoolParallelism = par; - long np = (long)(-par); // precompute initial ctl value - long ct = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK); - - commonPool = new ForkJoinPool(par, ct, fac, handler); + if (parallelism < 0 && // default 1 less than #cores + (parallelism = Runtime.getRuntime().availableProcessors() - 1) < 0) + parallelism = 0; + if (parallelism > MAX_CAP) + parallelism = MAX_CAP; + return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE, + "ForkJoinPool.commonPool-worker-"); } } diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinTask.java b/luni/src/main/java/java/util/concurrent/ForkJoinTask.java index 818788e..c6bc6de 100644 --- a/luni/src/main/java/java/util/concurrent/ForkJoinTask.java +++ b/luni/src/main/java/java/util/concurrent/ForkJoinTask.java @@ -32,8 +32,8 @@ import java.lang.reflect.Constructor; * * <p>A "main" {@code ForkJoinTask} begins execution when it is * explicitly submitted to a {@link ForkJoinPool}, or, if not already - * engaged in a ForkJoin computation, commenced in the {@link - * ForkJoinPool#commonPool()} via {@link #fork}, {@link #invoke}, or + * engaged in a ForkJoin computation, commenced in the {@code + * ForkJoinPool.commonPool()} via {@link #fork}, {@link #invoke}, or * related methods. Once started, it will usually in turn start other * subtasks. As indicated by the name of this class, many programs * using {@code ForkJoinTask} employ only methods {@link #fork} and @@ -74,10 +74,9 @@ import java.lang.reflect.Constructor; * but doing do requires three further considerations: (1) Completion * of few if any <em>other</em> tasks should be dependent on a task * that blocks on external synchronization or I/O. Event-style async - * tasks that are never joined (for example, those subclassing {@link - * CountedCompleter}) often fall into this category. (2) To minimize - * resource impact, tasks should be small; ideally performing only the - * (possibly) blocking action. (3) Unless the {@link + * tasks that are never joined often fall into this category. + * (2) To minimize resource impact, tasks should be small; ideally + * performing only the (possibly) blocking action. (3) Unless the {@link * ForkJoinPool.ManagedBlocker} API is used, or the number of possibly * blocked tasks is known to be less than the pool's {@link * ForkJoinPool#getParallelism} level, the pool cannot guarantee that @@ -120,13 +119,11 @@ import java.lang.reflect.Constructor; * <p>The ForkJoinTask class is not usually directly subclassed. * Instead, you subclass one of the abstract classes that support a * particular style of fork/join processing, typically {@link - * RecursiveAction} for most computations that do not return results, - * {@link RecursiveTask} for those that do, and {@link - * CountedCompleter} for those in which completed actions trigger - * other actions. Normally, a concrete ForkJoinTask subclass declares - * fields comprising its parameters, established in a constructor, and - * then defines a {@code compute} method that somehow uses the control - * methods supplied by this base class. + * RecursiveAction} for most computations that do not return results + * and {@link RecursiveTask} for those that do. Normally, a concrete + * ForkJoinTask subclass declares fields comprising its parameters, + * established in a constructor, and then defines a {@code compute} + * method that somehow uses the control methods supplied by this base class. * * <p>Method {@link #join} and its variants are appropriate for use * only when completion dependencies are acyclic; that is, the @@ -136,11 +133,11 @@ import java.lang.reflect.Constructor; * supports other methods and techniques (for example the use of * {@link Phaser}, {@link #helpQuiesce}, and {@link #complete}) that * may be of use in constructing custom subclasses for problems that - * are not statically structured as DAGs. To support such usages a + * are not statically structured as DAGs. To support such usages, a * ForkJoinTask may be atomically <em>tagged</em> with a {@code short} - * value using {@link #setForkJoinTaskTag} or {@link - * #compareAndSetForkJoinTaskTag} and checked using {@link - * #getForkJoinTaskTag}. The ForkJoinTask implementation does not use + * value using {@code setForkJoinTaskTag} or {@code + * compareAndSetForkJoinTaskTag} and checked using {@code + * getForkJoinTaskTag}. The ForkJoinTask implementation does not use * these {@code protected} methods or tags for any purpose, but they * may be of use in the construction of specialized subclasses. For * example, parallel graph traversals can use the supplied methods to @@ -178,7 +175,6 @@ import java.lang.reflect.Constructor; * execution. Serialization is not relied on during execution itself. * * @since 1.7 - * @hide * @author Doug Lea */ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { @@ -286,25 +282,35 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { */ private int externalAwaitDone() { int s; - ForkJoinPool.externalHelpJoin(this); - boolean interrupted = false; - while ((s = status) >= 0) { - if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { - synchronized (this) { - if (status >= 0) { - try { - wait(); - } catch (InterruptedException ie) { - interrupted = true; + ForkJoinPool cp = ForkJoinPool.common; + if ((s = status) >= 0) { + if (cp != null) { + if (this instanceof CountedCompleter) + s = cp.externalHelpComplete((CountedCompleter<?>)this); + else if (cp.tryExternalUnpush(this)) + s = doExec(); + } + if (s >= 0 && (s = status) >= 0) { + boolean interrupted = false; + do { + if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { + synchronized (this) { + if (status >= 0) { + try { + wait(); + } catch (InterruptedException ie) { + interrupted = true; + } + } + else + notifyAll(); } } - else - notifyAll(); - } + } while ((s = status) >= 0); + if (interrupted) + Thread.currentThread().interrupt(); } } - if (interrupted) - Thread.currentThread().interrupt(); return s; } @@ -313,9 +319,15 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { */ private int externalInterruptibleAwaitDone() throws InterruptedException { int s; + ForkJoinPool cp = ForkJoinPool.common; if (Thread.interrupted()) throw new InterruptedException(); - ForkJoinPool.externalHelpJoin(this); + if ((s = status) >= 0 && cp != null) { + if (this instanceof CountedCompleter) + cp.externalHelpComplete((CountedCompleter<?>)this); + else if (cp.tryExternalUnpush(this)) + doExec(); + } while ((s = status) >= 0) { if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { synchronized (this) { @@ -329,7 +341,6 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { return s; } - /** * Implementation for join, get, quietlyJoin. Directly handles * only cases of already-completed, external wait, and @@ -601,14 +612,9 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { /** * A version of "sneaky throw" to relay exceptions */ - static void rethrow(final Throwable ex) { - if (ex != null) { - if (ex instanceof Error) - throw (Error)ex; - if (ex instanceof RuntimeException) - throw (RuntimeException)ex; - throw uncheckedThrowable(ex, RuntimeException.class); - } + static void rethrow(Throwable ex) { + if (ex != null) + ForkJoinTask.<RuntimeException>uncheckedThrow(ex); } /** @@ -617,8 +623,8 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { * unchecked exceptions */ @SuppressWarnings("unchecked") static <T extends Throwable> - T uncheckedThrowable(final Throwable t, final Class<T> c) { - return (T)t; // rely on vacuous cast + void uncheckedThrow(Throwable t) throws T { + throw (T)t; // rely on vacuous cast } /** @@ -635,8 +641,8 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { /** * Arranges to asynchronously execute this task in the pool the - * current task is running in, if applicable, or using the {@link - * ForkJoinPool#commonPool()} if not {@link #inForkJoinPool}. While + * current task is running in, if applicable, or using the {@code + * ForkJoinPool.commonPool()} if not {@link #inForkJoinPool}. While * it is not necessarily enforced, it is a usage error to fork a * task more than once unless it has completed and been * reinitialized. Subsequent modifications to the state of this @@ -653,7 +659,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ((ForkJoinWorkerThread)t).workQueue.push(this); else - ForkJoinPool.commonPool.externalPush(this); + ForkJoinPool.common.externalPush(this); return this; } @@ -774,8 +780,6 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { * @param tasks the collection of tasks * @return the tasks argument, to simplify usage * @throws NullPointerException if tasks or any element are null - - * @hide */ public static <T extends ForkJoinTask<?>> Collection<T> invokeAll(Collection<T> tasks) { if (!(tasks instanceof RandomAccess) || !(tasks instanceof List<?>)) { @@ -831,7 +835,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { * <p>This method is designed to be invoked by <em>other</em> * tasks. To terminate the current task, you can just return or * throw an unchecked exception from its computation method, or - * invoke {@link #completeExceptionally}. + * invoke {@link #completeExceptionally(Throwable)}. * * @param mayInterruptIfRunning this value has no effect in the * default implementation because interrupts are not used to @@ -984,6 +988,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { // Messy in part because we measure in nanosecs, but wait in millisecs int s; long ms; long ns = unit.toNanos(timeout); + ForkJoinPool cp; if ((s = status) >= 0 && ns > 0L) { long deadline = System.nanoTime() + ns; ForkJoinPool p = null; @@ -995,8 +1000,12 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { w = wt.workQueue; p.helpJoinOnce(w, this); // no retries on failure } - else - ForkJoinPool.externalHelpJoin(this); + else if ((cp = ForkJoinPool.common) != null) { + if (this instanceof CountedCompleter) + cp.externalHelpComplete((CountedCompleter<?>)this); + else if (cp.tryExternalUnpush(this)) + doExec(); + } boolean canBlock = false; boolean interrupted = false; try { @@ -1004,7 +1013,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { if (w != null && w.qlock < 0) cancelIgnoringExceptions(this); else if (!canBlock) { - if (p == null || p.tryCompensate()) + if (p == null || p.tryCompensate(p.ctl)) canBlock = true; } else { @@ -1080,7 +1089,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { wt.pool.helpQuiescePool(wt.workQueue); } else - ForkJoinPool.externalHelpQuiescePool(); + ForkJoinPool.quiesceCommonPool(); } /** @@ -1145,7 +1154,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { Thread t; return (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? ((ForkJoinWorkerThread)t).workQueue.tryUnpush(this) : - ForkJoinPool.tryExternalUnpush(this)); + ForkJoinPool.common.tryExternalUnpush(this)); } /** @@ -1316,7 +1325,7 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { * * @param e the expected tag value * @param tag the new tag value - * @return true if successful; i.e., the current value was + * @return {@code true} if successful; i.e., the current value was * equal to e and is now tag. * @since 1.8 * @hide @@ -1370,6 +1379,24 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { } /** + * Adaptor for Runnables in which failure forces worker exception + */ + static final class RunnableExecuteAction extends ForkJoinTask<Void> { + final Runnable runnable; + RunnableExecuteAction(Runnable runnable) { + if (runnable == null) throw new NullPointerException(); + this.runnable = runnable; + } + public final Void getRawResult() { return null; } + public final void setRawResult(Void v) { } + public final boolean exec() { runnable.run(); return true; } + void internalPropagateException(Throwable ex) { + rethrow(ex); // rethrow outside exec() catches. + } + private static final long serialVersionUID = 5232453952276885070L; + } + + /** * Adaptor for Callables */ static final class AdaptedCallable<T> extends ForkJoinTask<T> @@ -1480,5 +1507,4 @@ public abstract class ForkJoinTask<V> implements Future<V>, Serializable { throw new Error(e); } } - } diff --git a/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java b/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java index f31763c..ae28700 100644 --- a/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java +++ b/luni/src/main/java/java/util/concurrent/ForkJoinWorkerThread.java @@ -14,11 +14,10 @@ package java.util.concurrent; * scheduling or execution. However, you can override initialization * and termination methods surrounding the main task processing loop. * If you do create such a subclass, you will also need to supply a - * custom {@link ForkJoinPool.ForkJoinWorkerThreadFactory} to use it - * in a {@code ForkJoinPool}. + * custom {@link ForkJoinPool.ForkJoinWorkerThreadFactory} to + * {@linkplain ForkJoinPool#ForkJoinPool use it} in a {@code ForkJoinPool}. * * @since 1.7 - * @hide * @author Doug Lea */ public class ForkJoinWorkerThread extends Thread { @@ -61,16 +60,17 @@ public class ForkJoinWorkerThread extends Thread { } /** - * Returns the index number of this thread in its pool. The - * returned value ranges from zero to the maximum number of - * threads (minus one) that have ever been created in the pool. - * This method may be useful for applications that track status or - * collect results per-worker rather than per-task. + * Returns the unique index number of this thread in its pool. + * The returned value ranges from zero to the maximum number of + * threads (minus one) that may exist in the pool, and does not + * change during the lifetime of the thread. This method may be + * useful for applications that track status or collect results + * per-worker-thread rather than per-task. * * @return the index number */ public int getPoolIndex() { - return workQueue.poolIndex; + return workQueue.poolIndex >>> 1; // ignore odd/even tag bit } /** diff --git a/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java b/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java index cff5dbf..a041fb1 100644 --- a/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java +++ b/luni/src/main/java/java/util/concurrent/LinkedTransferQueue.java @@ -50,7 +50,6 @@ import java.util.concurrent.locks.LockSupport; * the {@code LinkedTransferQueue} in another thread. * * @since 1.7 - * @hide * @author Doug Lea * @param <E> the type of elements held in this collection */ diff --git a/luni/src/main/java/java/util/concurrent/Phaser.java b/luni/src/main/java/java/util/concurrent/Phaser.java index a9adbe5..a97d187 100644 --- a/luni/src/main/java/java/util/concurrent/Phaser.java +++ b/luni/src/main/java/java/util/concurrent/Phaser.java @@ -227,7 +227,6 @@ import java.util.concurrent.locks.LockSupport; * of participants. * * @since 1.7 - * @hide * @author Doug Lea */ public class Phaser { diff --git a/luni/src/main/java/java/util/concurrent/RecursiveAction.java b/luni/src/main/java/java/util/concurrent/RecursiveAction.java index 8d666f6..e3a6340 100644 --- a/luni/src/main/java/java/util/concurrent/RecursiveAction.java +++ b/luni/src/main/java/java/util/concurrent/RecursiveAction.java @@ -131,7 +131,6 @@ package java.util.concurrent; * }}</pre> * * @since 1.7 - * @hide * @author Doug Lea */ public abstract class RecursiveAction extends ForkJoinTask<Void> { diff --git a/luni/src/main/java/java/util/concurrent/RecursiveTask.java b/luni/src/main/java/java/util/concurrent/RecursiveTask.java index 421c9d3..80baa52 100644 --- a/luni/src/main/java/java/util/concurrent/RecursiveTask.java +++ b/luni/src/main/java/java/util/concurrent/RecursiveTask.java @@ -34,7 +34,6 @@ package java.util.concurrent; * sequentially solve rather than subdividing. * * @since 1.7 - * @hide * @author Doug Lea */ public abstract class RecursiveTask<V> extends ForkJoinTask<V> { diff --git a/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java b/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java index a52351b..483981d 100644 --- a/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java +++ b/luni/src/main/java/java/util/concurrent/ScheduledThreadPoolExecutor.java @@ -690,7 +690,6 @@ public class ScheduledThreadPoolExecutor * @param value if {@code true}, remove on cancellation, else don't * @see #getRemoveOnCancelPolicy * @since 1.7 - * @hide */ public void setRemoveOnCancelPolicy(boolean value) { removeOnCancel = value; @@ -705,7 +704,6 @@ public class ScheduledThreadPoolExecutor * from the queue * @see #setRemoveOnCancelPolicy * @since 1.7 - * @hide */ public boolean getRemoveOnCancelPolicy() { return removeOnCancel; diff --git a/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java b/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java index a559321..5baf75f 100644 --- a/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java +++ b/luni/src/main/java/java/util/concurrent/ThreadLocalRandom.java @@ -30,7 +30,6 @@ import java.util.Random; * generation methods. * * @since 1.7 - * @hide * @author Doug Lea */ public class ThreadLocalRandom extends Random { diff --git a/luni/src/main/java/java/util/concurrent/TransferQueue.java b/luni/src/main/java/java/util/concurrent/TransferQueue.java index 9cd5773..4c2be6f 100644 --- a/luni/src/main/java/java/util/concurrent/TransferQueue.java +++ b/luni/src/main/java/java/util/concurrent/TransferQueue.java @@ -33,7 +33,6 @@ package java.util.concurrent; * and {@code transfer} are effectively synonymous. * * @since 1.7 - * @hide * @author Doug Lea * @param <E> the type of elements held in this collection */ diff --git a/luni/src/main/java/java/util/concurrent/atomic/Fences.java b/luni/src/main/java/java/util/concurrent/atomic/Fences.java index 7ecf45a..5714ba0 100644 --- a/luni/src/main/java/java/util/concurrent/atomic/Fences.java +++ b/luni/src/main/java/java/util/concurrent/atomic/Fences.java @@ -453,7 +453,6 @@ package java.util.concurrent.atomic; * * </dl> * - * @since 1.7 * @hide * @author Doug Lea */ diff --git a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java index 4c5e280..37aa9d0 100644 --- a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java +++ b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.java @@ -1255,7 +1255,6 @@ public abstract class AbstractQueuedLongSynchronizer * current thread, and {@code false} if the current thread * is at the head of the queue or the queue is empty * @since 1.7 - * @hide */ public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized diff --git a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java index 0350060..e711da5 100644 --- a/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java +++ b/luni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java @@ -1485,7 +1485,6 @@ public abstract class AbstractQueuedSynchronizer * current thread, and {@code false} if the current thread * is at the head of the queue or the queue is empty * @since 1.7 - * @hide */ public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized diff --git a/luni/src/main/java/java/util/jar/Attributes.java b/luni/src/main/java/java/util/jar/Attributes.java index 7e32897..483621b 100644 --- a/luni/src/main/java/java/util/jar/Attributes.java +++ b/luni/src/main/java/java/util/jar/Attributes.java @@ -288,7 +288,7 @@ public class Attributes implements Cloneable, Map<Object, Object> { * @param value * the value to store in this {@code Attributes}. * @return the value being stored. - * @exception ClassCastException + * @throws ClassCastException * when key is not an {@code Attributes.Name} or value is not * a {@code String}. */ @@ -307,9 +307,14 @@ public class Attributes implements Cloneable, Map<Object, Object> { * Attributes}). */ public void putAll(Map<?, ?> attrib) { - if (attrib == null || !(attrib instanceof Attributes)) { + if (attrib == null) { + throw new NullPointerException("attrib == null"); + } + + if (!(attrib instanceof Attributes)) { throw new ClassCastException(attrib.getClass().getName() + " not an Attributes"); } + this.map.putAll(attrib); } diff --git a/luni/src/main/java/java/util/jar/JarEntry.java b/luni/src/main/java/java/util/jar/JarEntry.java index 381dd52..bceef63 100644 --- a/luni/src/main/java/java/util/jar/JarEntry.java +++ b/luni/src/main/java/java/util/jar/JarEntry.java @@ -20,14 +20,14 @@ package java.util.jar; import java.io.IOException; import java.security.CodeSigner; import java.security.cert.CertPath; +import java.security.cert.CertPathValidator; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; import java.util.zip.ZipEntry; -import javax.security.auth.x500.X500Principal; /** * Represents a single file in a JAR archive together with the manifest @@ -39,7 +39,7 @@ import javax.security.auth.x500.X500Principal; public class JarEntry extends ZipEntry { private Attributes attributes; - JarFile parentJar; + final JarFile parentJar; CodeSigner signers[]; @@ -56,6 +56,7 @@ public class JarEntry extends ZipEntry { */ public JarEntry(String name) { super(name); + parentJar = null; } /** @@ -65,15 +66,35 @@ public class JarEntry extends ZipEntry { * The ZipEntry to obtain values from. */ public JarEntry(ZipEntry entry) { + this(entry, null); + } + + JarEntry(ZipEntry entry, JarFile parentJar) { super(entry); + this.parentJar = parentJar; + } + + /** + * Create a new {@code JarEntry} using the values obtained from the + * argument. + * + * @param je + * The {@code JarEntry} to obtain values from. + */ + public JarEntry(JarEntry je) { + super(je); + parentJar = je.parentJar; + attributes = je.attributes; + signers = je.signers; } + /** * Returns the {@code Attributes} object associated with this entry or * {@code null} if none exists. * * @return the {@code Attributes} for this entry. - * @exception IOException + * @throws IOException * If an error occurs obtaining the {@code Attributes}. * @see Attributes */ @@ -93,8 +114,12 @@ public class JarEntry extends ZipEntry { * entry or {@code null} if none exists. Make sure that the everything is * read from the input stream before calling this method, or else the method * returns {@code null}. + * <p> + * This method returns all the signers' unverified chains concatenated + * together in one array. To know which certificates were tied to the + * private keys that made the signatures on this entry, see + * {@link #getCodeSigners()} instead. * - * @return the certificate for this entry. * @see java.security.cert.Certificate */ public Certificate[] getCertificates() { @@ -105,7 +130,27 @@ public class JarEntry extends ZipEntry { if (jarVerifier == null) { return null; } - return jarVerifier.getCertificates(getName()); + + Certificate[][] certChains = jarVerifier.getCertificateChains(getName()); + if (certChains == null) { + return null; + } + + // Measure number of certs. + int count = 0; + for (Certificate[] chain : certChains) { + count += chain.length; + } + + // Create new array and copy all the certs into it. + Certificate[] certs = new Certificate[count]; + int i = 0; + for (Certificate[] chain : certChains) { + System.arraycopy(chain, 0, certs, i, chain.length); + i += chain.length; + } + + return certs; } void setAttributes(Attributes attrib) { @@ -113,86 +158,64 @@ public class JarEntry extends ZipEntry { } /** - * Create a new {@code JarEntry} using the values obtained from the - * argument. - * - * @param je - * The {@code JarEntry} to obtain values from. - */ - public JarEntry(JarEntry je) { - super(je); - parentJar = je.parentJar; - attributes = je.attributes; - signers = je.signers; - } - - /** * Returns the code signers for the digital signatures associated with the * JAR file. If there is no such code signer, it returns {@code null}. Make * sure that the everything is read from the input stream before calling * this method, or else the method returns {@code null}. + * <p> + * Only the digital signature on the entry is cryptographically verified. + * None of the certificates in the the {@link CertPath} returned from + * {@link CodeSigner#getSignerCertPath()} are verified and must be verified + * by the caller if needed. See {@link CertPathValidator} for more + * information. * - * @return the code signers for the JAR entry. + * @return an array of CodeSigner for this JAR entry. * @see CodeSigner */ public CodeSigner[] getCodeSigners() { + if (parentJar == null) { + return null; + } + + JarVerifier jarVerifier = parentJar.verifier; + if (jarVerifier == null) { + return null; + } + if (signers == null) { - signers = getCodeSigners(getCertificates()); + signers = getCodeSigners(jarVerifier.getCertificateChains(getName())); } if (signers == null) { return null; } - CodeSigner[] tmp = new CodeSigner[signers.length]; - System.arraycopy(signers, 0, tmp, 0, tmp.length); - return tmp; + return signers.clone(); } - private CodeSigner[] getCodeSigners(Certificate[] certs) { - if (certs == null) { + private CodeSigner[] getCodeSigners(Certificate[][] certChains) { + if (certChains == null) { return null; } - X500Principal prevIssuer = null; - ArrayList<Certificate> list = new ArrayList<Certificate>(certs.length); - ArrayList<CodeSigner> asigners = new ArrayList<CodeSigner>(); + ArrayList<CodeSigner> asigners = new ArrayList<CodeSigner>(certChains.length); - for (Certificate element : certs) { - if (!(element instanceof X509Certificate)) { - // Only X509Certificate-s are taken into account - see API spec. - continue; - } - X509Certificate x509 = (X509Certificate) element; - if (prevIssuer != null) { - X500Principal subj = x509.getSubjectX500Principal(); - if (!prevIssuer.equals(subj)) { - // Ok, this ends the previous chain, - // so transform this one into CertPath ... - addCodeSigner(asigners, list); - // ... and start a new one - list.clear(); - }// else { it's still the same chain } - - } - prevIssuer = x509.getIssuerX500Principal(); - list.add(x509); - } - if (!list.isEmpty()) { - addCodeSigner(asigners, list); - } - if (asigners.isEmpty()) { - // 'signers' is 'null' already - return null; + for (Certificate[] chain : certChains) { + addCodeSigner(asigners, chain); } CodeSigner[] tmp = new CodeSigner[asigners.size()]; asigners.toArray(tmp); return tmp; - } - private void addCodeSigner(ArrayList<CodeSigner> asigners, - List<Certificate> list) { + private void addCodeSigner(ArrayList<CodeSigner> asigners, Certificate[] certs) { + for (Certificate cert : certs) { + // Only X509Certificate instances are counted. See API spec. + if (!(cert instanceof X509Certificate)) { + return; + } + } + CertPath certPath = null; if (!isFactoryChecked) { try { @@ -207,7 +230,7 @@ public class JarEntry extends ZipEntry { return; } try { - certPath = factory.generateCertPath(list); + certPath = factory.generateCertPath(Arrays.asList(certs)); } catch (CertificateException ex) { // do nothing } diff --git a/luni/src/main/java/java/util/jar/JarFile.java b/luni/src/main/java/java/util/jar/JarFile.java index 5293a89..6b147f6 100644 --- a/luni/src/main/java/java/util/jar/JarFile.java +++ b/luni/src/main/java/java/util/jar/JarFile.java @@ -23,7 +23,9 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import libcore.io.Streams; @@ -48,28 +50,24 @@ public class JarFile extends ZipFile { // The manifest after it has been read from the JAR. private Manifest manifest; - // The entry for the MANIFEST.MF file before it is read. - private ZipEntry manifestEntry; + // The entry for the MANIFEST.MF file before the first call to getManifest(). + private byte[] manifestBytes; JarVerifier verifier; private boolean closed = false; static final class JarFileInputStream extends FilterInputStream { - private long count; - - private ZipEntry zipEntry; - - private JarVerifier.VerifierEntry entry; + private final JarVerifier.VerifierEntry entry; + private long count; private boolean done = false; - JarFileInputStream(InputStream is, ZipEntry ze, - JarVerifier.VerifierEntry e) { + JarFileInputStream(InputStream is, long size, JarVerifier.VerifierEntry e) { super(is); - zipEntry = ze; - count = zipEntry.getSize(); entry = e; + + count = size; } @Override @@ -140,6 +138,24 @@ public class JarFile extends ZipFile { } } + static final class JarFileEnumerator implements Enumeration<JarEntry> { + final Enumeration<? extends ZipEntry> ze; + final JarFile jf; + + JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) { + ze = zenum; + this.jf = jf; + } + + public boolean hasMoreElements() { + return ze.hasMoreElements(); + } + + public JarEntry nextElement() { + return new JarEntry(ze.nextElement(), jf /* parentJar */); + } + } + /** * Create a new {@code JarFile} using the contents of the specified file. * @@ -163,11 +179,7 @@ public class JarFile extends ZipFile { * If the file cannot be read. */ public JarFile(File file, boolean verify) throws IOException { - super(file); - if (verify) { - verifier = new JarVerifier(file.getPath()); - } - readMetaEntries(); + this(file, verify, ZipFile.OPEN_READ); } /** @@ -184,21 +196,30 @@ public class JarFile extends ZipFile { * If the file cannot be read. */ public JarFile(File file, boolean verify, int mode) throws IOException { - this(file, verify, mode, false); - } - - /** - * See previous constructor for other parameter definitions. - * @param chainCheck - * whether or not to check certificate chain signatures - * @hide - */ - public JarFile(File file, boolean verify, int mode, boolean chainCheck) throws IOException { super(file, mode); - if (verify) { - verifier = new JarVerifier(file.getPath(), chainCheck); + + // Step 1: Scan the central directory for meta entries (MANIFEST.mf + // & possibly the signature files) and read them fully. + HashMap<String, byte[]> metaEntries = readMetaEntries(this, verify); + + // Step 2: Construct a verifier with the information we have. + // Verification is possible *only* if the JAR file contains a manifest + // *AND* it contains signing related information (signature block + // files and the signature files). + // + // TODO: Is this really the behaviour we want if verify == true ? + // We silently skip verification for files that have no manifest or + // no signatures. + if (verify && metaEntries.containsKey(MANIFEST_NAME) && + metaEntries.size() > 1) { + // We create the manifest straight away, so that we can create + // the jar verifier as well. + manifest = new Manifest(metaEntries.get(MANIFEST_NAME), true); + verifier = new JarVerifier(getName(), manifest, metaEntries); + } else { + verifier = null; + manifestBytes = metaEntries.get(MANIFEST_NAME); } - readMetaEntries(); } /** @@ -226,21 +247,7 @@ public class JarFile extends ZipFile { * If file cannot be opened or read. */ public JarFile(String filename, boolean verify) throws IOException { - this(filename, verify, false); - } - - /** - * See previous constructor for other parameter definitions. - * @param chainCheck - * whether or not to check certificate chain signatures - * @hide - */ - public JarFile(String filename, boolean verify, boolean chainCheck) throws IOException { - super(filename); - if (verify) { - verifier = new JarVerifier(filename, chainCheck); - } - readMetaEntries(); + this(new File(filename), verify, ZipFile.OPEN_READ); } /** @@ -253,26 +260,6 @@ public class JarFile extends ZipFile { */ @Override public Enumeration<JarEntry> entries() { - class JarFileEnumerator implements Enumeration<JarEntry> { - Enumeration<? extends ZipEntry> ze; - - JarFile jf; - - JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) { - ze = zenum; - this.jf = jf; - } - - public boolean hasMoreElements() { - return ze.hasMoreElements(); - } - - public JarEntry nextElement() { - JarEntry je = new JarEntry(ze.nextElement()); - je.parentJar = jf; - return je; - } - } return new JarFileEnumerator(super.entries(), this); } @@ -303,73 +290,70 @@ public class JarFile extends ZipFile { if (closed) { throw new IllegalStateException("JarFile has been closed"); } + if (manifest != null) { return manifest; } - try { - InputStream is = super.getInputStream(manifestEntry); - if (verifier != null) { - verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is)); - is = super.getInputStream(manifestEntry); - } - try { - manifest = new Manifest(is, verifier != null); - } finally { - is.close(); - } - manifestEntry = null; // Can discard the entry now. - } catch (NullPointerException e) { - manifestEntry = null; + + // If manifest == null && manifestBytes == null, there's no manifest. + if (manifestBytes == null) { + return null; } + + // We hit this code path only if the verification isn't necessary. If + // we did decide to verify this file, we'd have created the Manifest and + // the associated Verifier in the constructor itself. + manifest = new Manifest(manifestBytes, false); + manifestBytes = null; + return manifest; } /** - * Called by the JarFile constructors, this method reads the contents of the + * Called by the JarFile constructors, Reads the contents of the * file's META-INF/ directory and picks out the MANIFEST.MF file and - * verifier signature files if they exist. Any signature files found are - * registered with the verifier. + * verifier signature files if they exist. * * @throws IOException * if there is a problem reading the jar file entries. + * @return a map of entry names to their {@code byte[]} content. */ - private void readMetaEntries() throws IOException { + static HashMap<String, byte[]> readMetaEntries(ZipFile zipFile, + boolean verificationRequired) throws IOException { // Get all meta directory entries - ZipEntry[] metaEntries = getMetaEntriesImpl(); - if (metaEntries == null) { - verifier = null; - return; - } + List<ZipEntry> metaEntries = getMetaEntries(zipFile); - boolean signed = false; + HashMap<String, byte[]> metaEntriesMap = new HashMap<String, byte[]>(); for (ZipEntry entry : metaEntries) { String entryName = entry.getName(); // Is this the entry for META-INF/MANIFEST.MF ? - if (manifestEntry == null && entryName.equalsIgnoreCase(MANIFEST_NAME)) { - manifestEntry = entry; + // + // TODO: Why do we need the containsKey check ? Shouldn't we discard + // files that contain duplicate entries like this as invalid ?. + if (entryName.equalsIgnoreCase(MANIFEST_NAME) && + !metaEntriesMap.containsKey(MANIFEST_NAME)) { + + metaEntriesMap.put(MANIFEST_NAME, Streams.readFully( + zipFile.getInputStream(entry))); + // If there is no verifier then we don't need to look any further. - if (verifier == null) { + if (!verificationRequired) { break; } - } else { + } else if (verificationRequired) { // Is this an entry that the verifier needs? - if (verifier != null - && (endsWithIgnoreCase(entryName, ".SF") - || endsWithIgnoreCase(entryName, ".DSA") - || endsWithIgnoreCase(entryName, ".RSA") - || endsWithIgnoreCase(entryName, ".EC"))) { - signed = true; - InputStream is = super.getInputStream(entry); - verifier.addMetaEntry(entryName, Streams.readFully(is)); + if (endsWithIgnoreCase(entryName, ".SF") + || endsWithIgnoreCase(entryName, ".DSA") + || endsWithIgnoreCase(entryName, ".RSA") + || endsWithIgnoreCase(entryName, ".EC")) { + InputStream is = zipFile.getInputStream(entry); + metaEntriesMap.put(entryName.toUpperCase(Locale.US), Streams.readFully(is)); } } } - // If there were no signature files, then no verifier work to do. - if (!signed) { - verifier = null; - } + return metaEntriesMap; } private static boolean endsWithIgnoreCase(String s, String suffix) { @@ -388,24 +372,21 @@ public class JarFile extends ZipFile { */ @Override public InputStream getInputStream(ZipEntry ze) throws IOException { - if (manifestEntry != null) { + if (manifestBytes != null) { getManifest(); } + if (verifier != null) { - verifier.setManifest(getManifest()); - if (manifest != null) { - verifier.mainAttributesEnd = manifest.getMainAttributesEnd(); - } if (verifier.readCertificates()) { verifier.removeMetaEntries(); - if (manifest != null) { - manifest.removeChunks(); - } + manifest.removeChunks(); + if (!verifier.isSignedJar()) { verifier = null; } } } + InputStream in = super.getInputStream(ze); if (in == null) { return null; @@ -417,7 +398,7 @@ public class JarFile extends ZipFile { if (entry == null) { return in; } - return new JarFileInputStream(in, ze, entry); + return new JarFileInputStream(in, ze.getSize(), entry); } /** @@ -434,20 +415,17 @@ public class JarFile extends ZipFile { if (ze == null) { return ze; } - JarEntry je = new JarEntry(ze); - je.parentJar = this; - return je; + return new JarEntry(ze, this /* parentJar */); } /** * Returns all the ZipEntry's that relate to files in the * JAR's META-INF directory. - * - * @return the list of ZipEntry's or {@code null} if there are none. */ - private ZipEntry[] getMetaEntriesImpl() { + private static List<ZipEntry> getMetaEntries(ZipFile zipFile) { List<ZipEntry> list = new ArrayList<ZipEntry>(8); - Enumeration<? extends ZipEntry> allEntries = entries(); + + Enumeration<? extends ZipEntry> allEntries = zipFile.entries(); while (allEntries.hasMoreElements()) { ZipEntry ze = allEntries.nextElement(); if (ze.getName().startsWith(META_DIR) @@ -455,12 +433,8 @@ public class JarFile extends ZipFile { list.add(ze); } } - if (list.size() == 0) { - return null; - } - ZipEntry[] result = new ZipEntry[list.size()]; - list.toArray(result); - return result; + + return list; } /** diff --git a/luni/src/main/java/java/util/jar/JarInputStream.java b/luni/src/main/java/java/util/jar/JarInputStream.java index 5e08b5d..585c135 100644 --- a/luni/src/main/java/java/util/jar/JarInputStream.java +++ b/luni/src/main/java/java/util/jar/JarInputStream.java @@ -21,9 +21,11 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.HashMap; import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import libcore.io.Streams; /** * The input stream from which the JAR file to be read may be fetched. It is @@ -31,15 +33,20 @@ import java.util.zip.ZipInputStream; * * @see ZipInputStream */ +// TODO: The semantics provided by this class are really weird. The jar file +// spec does not impose any ordering constraints on the entries of a jar file. +// In particular, the Manifest and META-INF directory *need not appear first*. This +// class will silently skip certificate checks for jar files where the manifest +// isn't the first entry. To do this correctly, we need O(input_stream_length) memory. public class JarInputStream extends ZipInputStream { private Manifest manifest; - private boolean eos = false; + private boolean verified = false; - private JarEntry mEntry; + private JarEntry currentJarEntry; - private JarEntry jarEntry; + private JarEntry pendingJarEntry; private boolean isMeta; @@ -60,38 +67,44 @@ public class JarInputStream extends ZipInputStream { */ public JarInputStream(InputStream stream, boolean verify) throws IOException { super(stream); - if (verify) { - verifier = new JarVerifier("JarInputStream"); - } - if ((mEntry = getNextJarEntry()) == null) { + + verifier = null; + pendingJarEntry = null; + currentJarEntry = null; + + if (getNextJarEntry() == null) { return; } - if (mEntry.getName().equalsIgnoreCase(JarFile.META_DIR)) { - mEntry = null; // modifies behavior of getNextJarEntry() + + if (currentJarEntry.getName().equalsIgnoreCase(JarFile.META_DIR)) { + // Fetch the next entry, in the hope that it's the manifest file. closeEntry(); - mEntry = getNextJarEntry(); + getNextJarEntry(); } - if (mEntry.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)) { - mEntry = null; - manifest = new Manifest(this, verify); + + if (currentJarEntry.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)) { + final byte[] manifestBytes = Streams.readFullyNoClose(this); + manifest = new Manifest(manifestBytes, verify); closeEntry(); + if (verify) { - verifier.setManifest(manifest); - if (manifest != null) { - verifier.mainAttributesEnd = manifest.getMainAttributesEnd(); - } + HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>(); + metaEntries.put(JarFile.MANIFEST_NAME, manifestBytes); + verifier = new JarVerifier("JarInputStream", manifest, metaEntries); } - - } else { - Attributes temp = new Attributes(3); - temp.map.put("hidden", null); - mEntry.setAttributes(temp); - /* - * if not from the first entry, we will not get enough - * information,so no verify will be taken out. - */ - verifier = null; } + + // There was no manifest available, so we should return the current + // entry the next time getNextEntry is called. + pendingJarEntry = currentJarEntry; + currentJarEntry = null; + + // If the manifest isn't the first entry, we will not have enough + // information to perform verification on entries that precede it. + // + // TODO: Should we throw if verify == true in this case ? + // TODO: We need all meta entries to be placed before the manifest + // as well. } /** @@ -138,32 +151,39 @@ public class JarInputStream extends ZipInputStream { */ @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { - if (mEntry != null) { + if (currentJarEntry == null) { return -1; } + int r = super.read(buffer, byteOffset, byteCount); - if (verStream != null && !eos) { + // verifier can be null if we've been asked not to verify or if + // the manifest wasn't found. + // + // verStream will be null if we're reading the manifest or if we have + // no signatures or if the digest for this entry isn't present in the + // manifest. + if (verifier != null && verStream != null && !verified) { if (r == -1) { - eos = true; - if (verifier != null) { - if (isMeta) { - verifier.addMetaEntry(jarEntry.getName(), - ((ByteArrayOutputStream) verStream) - .toByteArray()); - try { - verifier.readCertificates(); - } catch (SecurityException e) { - verifier = null; - throw e; - } - } else { - ((JarVerifier.VerifierEntry) verStream).verify(); + // We've hit the end of this stream for the first time, so attempt + // a verification. + verified = true; + if (isMeta) { + verifier.addMetaEntry(currentJarEntry.getName(), + ((ByteArrayOutputStream) verStream).toByteArray()); + try { + verifier.readCertificates(); + } catch (SecurityException e) { + verifier = null; + throw e; } + } else { + ((JarVerifier.VerifierEntry) verStream).verify(); } } else { verStream.write(buffer, byteOffset, r); } } + return r; } @@ -177,26 +197,47 @@ public class JarInputStream extends ZipInputStream { */ @Override public ZipEntry getNextEntry() throws IOException { - if (mEntry != null) { - jarEntry = mEntry; - mEntry = null; - jarEntry.setAttributes(null); - } else { - jarEntry = (JarEntry) super.getNextEntry(); - if (jarEntry == null) { - return null; - } - if (verifier != null) { - isMeta = jarEntry.getName().toUpperCase(Locale.US).startsWith(JarFile.META_DIR); - if (isMeta) { - verStream = new ByteArrayOutputStream(); - } else { - verStream = verifier.initEntry(jarEntry.getName()); - } + // NOTE: This function must update the value of currentJarEntry + // as a side effect. + + if (pendingJarEntry != null) { + JarEntry pending = pendingJarEntry; + pendingJarEntry = null; + currentJarEntry = pending; + return pending; + } + + currentJarEntry = (JarEntry) super.getNextEntry(); + if (currentJarEntry == null) { + return null; + } + + if (verifier != null) { + isMeta = currentJarEntry.getName().toUpperCase(Locale.US).startsWith(JarFile.META_DIR); + if (isMeta) { + final int entrySize = (int) currentJarEntry.getSize(); + verStream = new ByteArrayOutputStream(entrySize > 0 ? entrySize : 8192); + } else { + verStream = verifier.initEntry(currentJarEntry.getName()); } } - eos = false; - return jarEntry; + + verified = false; + return currentJarEntry; + } + + @Override + public void closeEntry() throws IOException { + // NOTE: This was the old behavior. A call to closeEntry() before the + // first call to getNextEntry should be a no-op. If we don't return early + // here, the super class will close pendingJarEntry for us and reads will + // fail. + if (pendingJarEntry != null) { + return; + } + + super.closeEntry(); + currentJarEntry = null; } @Override diff --git a/luni/src/main/java/java/util/jar/JarVerifier.java b/luni/src/main/java/java/util/jar/JarVerifier.java index 8185c6d..467e298 100644 --- a/luni/src/main/java/java/util/jar/JarVerifier.java +++ b/luni/src/main/java/java/util/jar/JarVerifier.java @@ -17,6 +17,7 @@ package java.util.jar; +import org.apache.harmony.security.utils.JarUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.OutputStream; @@ -31,10 +32,7 @@ import java.util.Hashtable; import java.util.Iterator; import java.util.Locale; import java.util.Map; -import java.util.StringTokenizer; -import java.util.Vector; import libcore.io.Base64; -import org.apache.harmony.security.utils.JarUtils; /** * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage @@ -63,44 +61,42 @@ class JarVerifier { }; private final String jarName; + private final Manifest manifest; + private final HashMap<String, byte[]> metaEntries; + private final int mainAttributesEnd; - private Manifest man; - - private HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>(5); - - private final Hashtable<String, HashMap<String, Attributes>> signatures = new Hashtable<String, HashMap<String, Attributes>>( - 5); + private final Hashtable<String, HashMap<String, Attributes>> signatures = + new Hashtable<String, HashMap<String, Attributes>>(5); - private final Hashtable<String, Certificate[]> certificates = new Hashtable<String, Certificate[]>( - 5); + private final Hashtable<String, Certificate[]> certificates = + new Hashtable<String, Certificate[]>(5); - private final Hashtable<String, Certificate[]> verifiedEntries = new Hashtable<String, Certificate[]>(); - - int mainAttributesEnd; - - /** Whether or not to check certificate chain signatures. */ - private final boolean chainCheck; + private final Hashtable<String, Certificate[][]> verifiedEntries = + new Hashtable<String, Certificate[][]>(); /** * Stores and a hash and a message digest and verifies that massage digest * matches the hash. */ - class VerifierEntry extends OutputStream { + static class VerifierEntry extends OutputStream { - private String name; + private final String name; - private MessageDigest digest; + private final MessageDigest digest; - private byte[] hash; + private final byte[] hash; - private Certificate[] certificates; + private final Certificate[][] certChains; + + private final Hashtable<String, Certificate[][]> verifiedEntries; VerifierEntry(String name, MessageDigest digest, byte[] hash, - Certificate[] certificates) { + Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) { this.name = name; this.digest = digest; this.hash = hash; - this.certificates = certificates; + this.certChains = certChains; + this.verifiedEntries = verifedEntries; } /** @@ -122,7 +118,7 @@ class JarVerifier { /** * Verifies that the digests stored in the manifest match the decrypted * digests from the .SF file. This indicates the validity of the - * signing, not the integrity of the file, as it's digest must be + * signing, not the integrity of the file, as its digest must be * calculated and verified when its contents are read. * * @throws SecurityException @@ -133,40 +129,33 @@ class JarVerifier { void verify() { byte[] d = digest.digest(); if (!MessageDigest.isEqual(d, Base64.decode(hash))) { - throw invalidDigest(JarFile.MANIFEST_NAME, name, jarName); + throw invalidDigest(JarFile.MANIFEST_NAME, name, name); } - verifiedEntries.put(name, certificates); + verifiedEntries.put(name, certChains); } - } - private SecurityException invalidDigest(String signatureFile, String name, String jarName) { + private static SecurityException invalidDigest(String signatureFile, String name, + String jarName) { throw new SecurityException(signatureFile + " has invalid digest for " + name + " in " + jarName); } - private SecurityException failedVerification(String jarName, String signatureFile) { + private static SecurityException failedVerification(String jarName, String signatureFile) { throw new SecurityException(jarName + " failed verification of " + signatureFile); } /** - * Convenience constructor for backward compatibility. - */ - JarVerifier(String name) { - this(name, false); - } - - /** * Constructs and returns a new instance of {@code JarVerifier}. * * @param name * the name of the JAR file being verified. - * @param chainCheck - * whether to check the certificate chain signatures */ - JarVerifier(String name, boolean chainCheck) { + JarVerifier(String name, Manifest manifest, HashMap<String, byte[]> metaEntries) { jarName = name; - this.chainCheck = chainCheck; + this.manifest = manifest; + this.metaEntries = metaEntries; + this.mainAttributesEnd = manifest.getMainAttributesEnd(); } /** @@ -185,17 +174,17 @@ class JarVerifier { // If no manifest is present by the time an entry is found, // verification cannot occur. If no signature files have // been found, do not verify. - if (man == null || signatures.size() == 0) { + if (manifest == null || signatures.isEmpty()) { return null; } - Attributes attributes = man.getAttributes(name); + Attributes attributes = manifest.getAttributes(name); // entry has no digest if (attributes == null) { return null; } - ArrayList<Certificate> certs = new ArrayList<Certificate>(); + ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>(); Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, HashMap<String, Attributes>> entry = it.next(); @@ -203,15 +192,18 @@ class JarVerifier { if (hm.get(name) != null) { // Found an entry for entry name in .SF file String signatureFile = entry.getKey(); - certs.addAll(getSignerCertificates(signatureFile, certificates)); + Certificate[] certChain = certificates.get(signatureFile); + if (certChain != null) { + certChains.add(certChain); + } } } // entry is not signed - if (certs.isEmpty()) { + if (certChains.isEmpty()) { return null; } - Certificate[] certificatesArray = certs.toArray(new Certificate[certs.size()]); + Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]); for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) { final String algorithm = DIGEST_ALGORITHMS[i]; @@ -223,9 +215,8 @@ class JarVerifier { try { return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes, - certificatesArray); - } catch (NoSuchAlgorithmException e) { - // ignored + certChainsArray, verifiedEntries); + } catch (NoSuchAlgorithmException ignored) { } } return null; @@ -266,18 +257,15 @@ class JarVerifier { * corresponding signature file. */ synchronized boolean readCertificates() { - if (metaEntries == null) { + if (metaEntries.isEmpty()) { return false; } + Iterator<String> it = metaEntries.keySet().iterator(); while (it.hasNext()) { String key = it.next(); if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) { verifyCertificate(key); - // Check for recursive class load - if (metaEntries == null) { - return false; - } it.remove(); } } @@ -295,9 +283,9 @@ class JarVerifier { return; } - byte[] manifest = metaEntries.get(JarFile.MANIFEST_NAME); + byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME); // Manifest entry is required for any verifications. - if (manifest == null) { + if (manifestBytes == null) { return; } @@ -305,15 +293,7 @@ class JarVerifier { try { Certificate[] signerCertChain = JarUtils.verifySignature( new ByteArrayInputStream(sfBytes), - new ByteArrayInputStream(sBlockBytes), - chainCheck); - /* - * Recursive call in loading security provider related class which - * is in a signed JAR. - */ - if (metaEntries == null) { - return; - } + new ByteArrayInputStream(sBlockBytes)); if (signerCertChain != null) { certificates.put(signatureFile, signerCertChain); } @@ -350,22 +330,22 @@ class JarVerifier { // such verification. if (mainAttributesEnd > 0 && !createdBySigntool) { String digestAttribute = "-Digest-Manifest-Main-Attributes"; - if (!verify(attributes, digestAttribute, manifest, 0, mainAttributesEnd, false, true)) { + if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) { throw failedVerification(jarName, signatureFile); } } // Use .SF to verify the whole manifest. String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest"; - if (!verify(attributes, digestAttribute, manifest, 0, manifest.length, false, false)) { + if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) { Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Attributes> entry = it.next(); - Manifest.Chunk chunk = man.getChunk(entry.getKey()); + Manifest.Chunk chunk = manifest.getChunk(entry.getKey()); if (chunk == null) { return; } - if (!verify(entry.getValue(), "-Digest", manifest, + if (!verify(entry.getValue(), "-Digest", manifestBytes, chunk.start, chunk.end, createdBySigntool, false)) { throw invalidDigest(signatureFile, entry.getKey(), jarName); } @@ -376,16 +356,6 @@ class JarVerifier { } /** - * Associate this verifier with the specified {@link Manifest} object. - * - * @param mf - * a {@code java.util.jar.Manifest} object. - */ - void setManifest(Manifest mf) { - man = mf; - } - - /** * Returns a <code>boolean</code> indication of whether or not the * associated jar file is signed. * @@ -424,58 +394,23 @@ class JarVerifier { } /** - * Returns all of the {@link java.security.cert.Certificate} instances that + * Returns all of the {@link java.security.cert.Certificate} chains that * were used to verify the signature on the JAR entry called - * {@code name}. + * {@code name}. Callers must not modify the returned arrays. * * @param name * the name of a JAR entry. - * @return an array of {@link java.security.cert.Certificate}. + * @return an array of {@link java.security.cert.Certificate} chains. */ - Certificate[] getCertificates(String name) { - Certificate[] verifiedCerts = verifiedEntries.get(name); - if (verifiedCerts == null) { - return null; - } - return verifiedCerts.clone(); + Certificate[][] getCertificateChains(String name) { + return verifiedEntries.get(name); } /** * Remove all entries from the internal collection of data held about each * JAR entry in the {@code META-INF} directory. - * - * @see #addMetaEntry(String, byte[]) */ void removeMetaEntries() { - metaEntries = null; - } - - /** - * Returns a {@code Vector} of all of the - * {@link java.security.cert.Certificate}s that are associated with the - * signing of the named signature file. - * - * @param signatureFileName - * the name of a signature file. - * @param certificates - * a {@code Map} of all of the certificate chains discovered so - * far while attempting to verify the JAR that contains the - * signature file {@code signatureFileName}. This object is - * previously set in the course of one or more calls to - * {@link #verifyJarSignatureFile(String, String, String, Map, Map)} - * where it was passed as the last argument. - * @return all of the {@code Certificate} entries for the signer of the JAR - * whose actions led to the creation of the named signature file. - */ - public static Vector<Certificate> getSignerCertificates( - String signatureFileName, Map<String, Certificate[]> certificates) { - Vector<Certificate> result = new Vector<Certificate>(); - Certificate[] certChain = certificates.get(signatureFileName); - if (certChain != null) { - for (Certificate element : certChain) { - result.add(element); - } - } - return result; + metaEntries.clear(); } } diff --git a/luni/src/main/java/java/util/jar/Manifest.java b/luni/src/main/java/java/util/jar/Manifest.java index b6ebddc..6a3936d 100644 --- a/luni/src/main/java/java/util/jar/Manifest.java +++ b/luni/src/main/java/java/util/jar/Manifest.java @@ -17,11 +17,9 @@ package java.util.jar; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; @@ -43,26 +41,12 @@ public class Manifest implements Cloneable { private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' }; - private static final Field BAIS_BUF = getByteArrayInputStreamField("buf"); - private static final Field BAIS_POS = getByteArrayInputStreamField("pos"); + private final Attributes mainAttributes; + private final HashMap<String, Attributes> entries; - private static Field getByteArrayInputStreamField(String name) { - try { - Field f = ByteArrayInputStream.class.getDeclaredField(name); - f.setAccessible(true); - return f; - } catch (Exception ex) { - throw new AssertionError(ex); - } - } - - private Attributes mainAttributes = new Attributes(); - - private HashMap<String, Attributes> entries = new HashMap<String, Attributes>(); - - static class Chunk { - int start; - int end; + static final class Chunk { + final int start; + final int end; Chunk(int start, int end) { this.start = start; @@ -82,6 +66,8 @@ public class Manifest implements Cloneable { * Creates a new {@code Manifest} instance. */ public Manifest() { + entries = new HashMap<String, Attributes>(); + mainAttributes = new Attributes(); } /** @@ -94,7 +80,8 @@ public class Manifest implements Cloneable { * if an IO error occurs while creating this {@code Manifest} */ public Manifest(InputStream is) throws IOException { - read(is); + this(); + read(Streams.readFully(is)); } /** @@ -111,11 +98,12 @@ public class Manifest implements Cloneable { .getEntries()).clone(); } - Manifest(InputStream is, boolean readChunks) throws IOException { + Manifest(byte[] manifestBytes, boolean readChunks) throws IOException { + this(); if (readChunks) { chunks = new HashMap<String, Chunk>(); } - read(is); + read(manifestBytes); } /** @@ -192,58 +180,20 @@ public class Manifest implements Cloneable { * If an error occurs reading the manifest. */ public void read(InputStream is) throws IOException { - byte[] buf; - if (is instanceof ByteArrayInputStream) { - buf = exposeByteArrayInputStreamBytes((ByteArrayInputStream) is); - } else { - buf = Streams.readFullyNoClose(is); - } + read(Streams.readFullyNoClose(is)); + } + private void read(byte[] buf) throws IOException { if (buf.length == 0) { return; } - // a workaround for HARMONY-5662 - // replace EOF and NUL with another new line - // which does not trigger an error - byte b = buf[buf.length - 1]; - if (b == 0 || b == 26) { - buf[buf.length - 1] = '\n'; - } - ManifestReader im = new ManifestReader(buf, mainAttributes); mainEnd = im.getEndOfMainSection(); im.readEntries(entries, chunks); } /** - * Returns a byte[] containing all the bytes from a ByteArrayInputStream. - * Where possible, this returns the actual array rather than a copy. - */ - private static byte[] exposeByteArrayInputStreamBytes(ByteArrayInputStream bais) { - byte[] buffer; - synchronized (bais) { - byte[] buf; - int pos; - try { - buf = (byte[]) BAIS_BUF.get(bais); - pos = BAIS_POS.getInt(bais); - } catch (IllegalAccessException iae) { - throw new AssertionError(iae); - } - int available = bais.available(); - if (pos == 0 && buf.length == available) { - buffer = buf; - } else { - buffer = new byte[available]; - System.arraycopy(buf, pos, buffer, 0, available); - } - bais.skip(available); - } - return buffer; - } - - /** * Returns the hash code for this instance. * * @return this {@code Manifest}'s hashCode. diff --git a/luni/src/main/java/java/util/jar/StrictJarFile.java b/luni/src/main/java/java/util/jar/StrictJarFile.java new file mode 100644 index 0000000..4a8af5f --- /dev/null +++ b/luni/src/main/java/java/util/jar/StrictJarFile.java @@ -0,0 +1,232 @@ +/* + * 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. + */ + + +package java.util.jar; + +import dalvik.system.CloseGuard; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.security.cert.Certificate; +import java.util.HashMap; +import java.util.Iterator; +import java.util.zip.Inflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import libcore.io.IoUtils; +import libcore.io.Streams; + +/** + * A subset of the JarFile API implemented as a thin wrapper over + * system/core/libziparchive. + * + * @hide for internal use only. Not API compatible (or as forgiving) as + * {@link java.util.jar.JarFile} + */ +public final class StrictJarFile { + + private final long nativeHandle; + + // NOTE: It's possible to share a file descriptor with the native + // code, at the cost of some additional complexity. + private final RandomAccessFile raf; + + private final Manifest manifest; + private final JarVerifier verifier; + + private final boolean isSigned; + + private final CloseGuard guard = CloseGuard.get(); + private boolean closed; + + public StrictJarFile(String fileName) throws IOException { + this.nativeHandle = nativeOpenJarFile(fileName); + this.raf = new RandomAccessFile(fileName, "r"); + + try { + // Read the MANIFEST and signature files up front and try to + // parse them. We never want to accept a JAR File with broken signatures + // or manifests, so it's best to throw as early as possible. + HashMap<String, byte[]> metaEntries = getMetaEntries(); + this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true); + this.verifier = new JarVerifier(fileName, manifest, metaEntries); + + isSigned = verifier.readCertificates() && verifier.isSignedJar(); + } catch (IOException ioe) { + nativeClose(this.nativeHandle); + throw ioe; + } + + guard.open("close"); + } + + public Manifest getManifest() { + return manifest; + } + + public Iterator<ZipEntry> iterator() throws IOException { + return new EntryIterator(nativeHandle, ""); + } + + public ZipEntry findEntry(String name) { + return nativeFindEntry(nativeHandle, name); + } + + /** + * Return all certificate chains for a given {@link ZipEntry} belonging to this jar. + * This method MUST be called only after fully exhausting the InputStream belonging + * to this entry. + * + * Returns {@code null} if this jar file isn't signed or if this method is + * called before the stream is processed. + */ + public Certificate[][] getCertificateChains(ZipEntry ze) { + if (isSigned) { + return verifier.getCertificateChains(ze.getName()); + } + + return null; + } + + /** + * Return all certificates for a given {@link ZipEntry} belonging to this jar. + * This method MUST be called only after fully exhausting the InputStream belonging + * to this entry. + * + * Returns {@code null} if this jar file isn't signed or if this method is + * called before the stream is processed. + * + * @deprecated Switch callers to use getCertificateChains instead + */ + @Deprecated + public Certificate[] getCertificates(ZipEntry ze) { + if (isSigned) { + Certificate[][] certChains = verifier.getCertificateChains(ze.getName()); + + // Measure number of certs. + int count = 0; + for (Certificate[] chain : certChains) { + count += chain.length; + } + + // Create new array and copy all the certs into it. + Certificate[] certs = new Certificate[count]; + int i = 0; + for (Certificate[] chain : certChains) { + System.arraycopy(chain, 0, certs, i, chain.length); + i += chain.length; + } + + return certs; + } + + return null; + } + + public InputStream getInputStream(ZipEntry ze) { + final InputStream is = getZipInputStream(ze); + + if (isSigned) { + JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName()); + if (entry == null) { + return is; + } + + return new JarFile.JarFileInputStream(is, ze.getSize(), entry); + } + + return is; + } + + public void close() throws IOException { + if (!closed) { + guard.close(); + + nativeClose(nativeHandle); + IoUtils.closeQuietly(raf); + closed = true; + } + } + + private InputStream getZipInputStream(ZipEntry ze) { + if (ze.getMethod() == ZipEntry.STORED) { + return new ZipFile.RAFStream(raf, ze.getDataOffset(), + ze.getDataOffset() + ze.getSize()); + } else { + final ZipFile.RAFStream wrapped = new ZipFile.RAFStream( + raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize()); + + int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L)); + return new ZipFile.ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze); + } + } + + static final class EntryIterator implements Iterator<ZipEntry> { + private final long iterationHandle; + private ZipEntry nextEntry; + + EntryIterator(long nativeHandle, String prefix) throws IOException { + iterationHandle = nativeStartIteration(nativeHandle, prefix); + } + + public ZipEntry next() { + if (nextEntry != null) { + final ZipEntry ze = nextEntry; + nextEntry = null; + return ze; + } + + return nativeNextEntry(iterationHandle); + } + + public boolean hasNext() { + if (nextEntry != null) { + return true; + } + + final ZipEntry ze = nativeNextEntry(iterationHandle); + if (ze == null) { + return false; + } + + nextEntry = ze; + return true; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private HashMap<String, byte[]> getMetaEntries() throws IOException { + HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>(); + + Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/"); + while (entryIterator.hasNext()) { + final ZipEntry entry = entryIterator.next(); + metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry))); + } + + return metaEntries; + } + + private static native long nativeOpenJarFile(String fileName) throws IOException; + private static native long nativeStartIteration(long nativeHandle, String prefix); + private static native ZipEntry nativeNextEntry(long iterationHandle); + private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName); + private static native void nativeClose(long nativeHandle); +} diff --git a/luni/src/main/java/java/util/logging/SocketHandler.java b/luni/src/main/java/java/util/logging/SocketHandler.java index 85a9e6c..48bfc0e 100644 --- a/luni/src/main/java/java/util/logging/SocketHandler.java +++ b/luni/src/main/java/java/util/logging/SocketHandler.java @@ -108,12 +108,9 @@ public class SocketHandler extends StreamHandler { // check the validity of the port number int p = 0; try { - p = Integer.parseInt(port); + p = Integer.parsePositiveInt(port); } catch (NumberFormatException e) { - throw new IllegalArgumentException("Illegal port argument"); - } - if (p <= 0) { - throw new IllegalArgumentException("Illegal port argument"); + throw new IllegalArgumentException("Illegal port argument " + port); } // establish the network connection try { diff --git a/luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java b/luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java index 8f2d0aa..154c71e 100644 --- a/luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java +++ b/luni/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java @@ -24,10 +24,12 @@ package java.util.prefs; */ class FilePreferencesFactoryImpl implements PreferencesFactory { // user root preferences - private static final Preferences USER_ROOT = new FilePreferencesImpl(true); + private static final Preferences USER_ROOT = new FilePreferencesImpl( + System.getProperty("user.home") + "/.java/.userPrefs", true); // system root preferences - private static final Preferences SYSTEM_ROOT = new FilePreferencesImpl(false); + private static final Preferences SYSTEM_ROOT = new FilePreferencesImpl( + System.getProperty("java.home") + "/.systemPrefs", false); public FilePreferencesFactoryImpl() { } @@ -39,5 +41,4 @@ class FilePreferencesFactoryImpl implements PreferencesFactory { public Preferences systemRoot() { return SYSTEM_ROOT; } - } diff --git a/luni/src/main/java/java/util/prefs/FilePreferencesImpl.java b/luni/src/main/java/java/util/prefs/FilePreferencesImpl.java index bd367a6..de1ead4 100644 --- a/luni/src/main/java/java/util/prefs/FilePreferencesImpl.java +++ b/luni/src/main/java/java/util/prefs/FilePreferencesImpl.java @@ -30,20 +30,15 @@ import java.util.Set; * TODO some sync mechanism with backend, Performance - check file edit date * * @since 1.4 + * + * @hide */ -class FilePreferencesImpl extends AbstractPreferences { - +public class FilePreferencesImpl extends AbstractPreferences { //prefs file name private static final String PREFS_FILE_NAME = "prefs.xml"; - //home directory for user prefs - private static String USER_HOME = System.getProperty("user.home") + "/.java/.userPrefs"; - - //home directory for system prefs - private static String SYSTEM_HOME = System.getProperty("java.home") + "/.systemPrefs"; - //file path for this preferences node - private String path; + private final String path; //internal cache for prefs key-value pair private Properties prefs; @@ -67,13 +62,15 @@ class FilePreferencesImpl extends AbstractPreferences { */ /** - * Construct root <code>FilePreferencesImpl</code> instance, construct - * user root if userNode is true, system root otherwise + * Construct root <code>FilePreferencesImpl</code> instance rooted + * at the given path. + * + * @hide */ - FilePreferencesImpl(boolean userNode) { + public FilePreferencesImpl(String path, boolean isUserNode) { super(null, ""); - this.userNode = userNode; - path = userNode ? USER_HOME : SYSTEM_HOME; + this.path = path; + this.userNode = isUserNode; initPrefs(); } diff --git a/luni/src/main/java/java/util/prefs/Preferences.java b/luni/src/main/java/java/util/prefs/Preferences.java index b808052..342be70 100644 --- a/luni/src/main/java/java/util/prefs/Preferences.java +++ b/luni/src/main/java/java/util/prefs/Preferences.java @@ -98,8 +98,17 @@ public abstract class Preferences { */ public static final int MAX_VALUE_LENGTH = 8192; - //factory used to get user/system prefs root - private static final PreferencesFactory factory = findPreferencesFactory(); + // factory used to get user/system prefs root + private static volatile PreferencesFactory factory = findPreferencesFactory(); + + /** + * @hide for testing only. + */ + public static PreferencesFactory setPreferencesFactory(PreferencesFactory pf) { + PreferencesFactory previous = factory; + factory = pf; + return previous; + } private static PreferencesFactory findPreferencesFactory() { // Try the system property first... @@ -780,6 +789,11 @@ public abstract class Preferences { public abstract void sync() throws BackingStoreException; /** + * <strong>Legacy code; do not use.</strong> On Android, the Preference nodes + * corresponding to the "system" and "user" preferences are stored in sections + * of the file system that are inaccessible to apps. Further, allowing apps to set + * "system wide" preferences is contrary to android's security model. + * * Returns the system preference node for the package of the given class. * The absolute path of the returned node is one slash followed by the given * class's full package name, replacing each period character ('.') with @@ -796,11 +810,16 @@ public abstract class Preferences { * @throws NullPointerException * if the given class is {@code null}. */ - public static Preferences systemNodeForPackage (Class<?> c) { + public static Preferences systemNodeForPackage(Class<?> c) { return factory.systemRoot().node(getNodeName(c)); } /** + * <strong>Legacy code; do not use.</strong> On Android, the Preference nodes + * corresponding to the "system" and "user" preferences are stored in sections + * of the file system that are inaccessible to apps. Further, allowing apps to set + * "system wide" preferences is contrary to android's security model. + * * Returns the root node of the system preference hierarchy. * * @return the system preference hierarchy root node. @@ -810,6 +829,13 @@ public abstract class Preferences { } /** + * + * <strong>Legacy code; do not use.</strong> On Android, the Preference nodes + * corresponding to the "system" and "user" preferences are stored in sections + * of the file system that are inaccessible to apps. Further, allowing apps to set + * "system wide" preferences is contrary to android's security model. + * + * <p> * Returns the user preference node for the package of the given class. * The absolute path of the returned node is one slash followed by the given * class's full package name, replacing each period character ('.') with @@ -820,13 +846,11 @@ public abstract class Preferences { * by this method won't necessarily be persisted until the method {@code * flush()} is invoked. * - * @param c - * the given class. * @return the user preference node for the package of the given class. * @throws NullPointerException * if the given class is {@code null}. */ - public static Preferences userNodeForPackage (Class<?> c) { + public static Preferences userNodeForPackage(Class<?> c) { return factory.userRoot().node(getNodeName(c)); } @@ -840,6 +864,11 @@ public abstract class Preferences { } /** + * <strong>Legacy code; do not use.</strong> On Android, the Preference nodes + * corresponding to the "system" and "user" preferences are stored in sections + * of the file system that are inaccessible to apps. Further, allowing apps to set + * "system wide" preferences is contrary to android's security model. + * * Returns the root node of the user preference hierarchy. * * @return the user preference hierarchy root node. diff --git a/luni/src/main/java/java/util/regex/MatchResult.java b/luni/src/main/java/java/util/regex/MatchResult.java index 76c17a8..c77206d 100644 --- a/luni/src/main/java/java/util/regex/MatchResult.java +++ b/luni/src/main/java/java/util/regex/MatchResult.java @@ -19,79 +19,65 @@ package java.util.regex; /** * Holds the results of a successful match of a {@link Pattern} against a - * given string. The result is divided into groups, with one group for each - * pair of parentheses in the regular expression and an additional group for - * the whole regular expression. The start, end, and contents of each group - * can be queried. - * - * @see Matcher - * @see Matcher#toMatchResult() + * given string. Typically this is an instance of {@link Matcher}, but + * since that's a mutable class it's also possible to freeze its current + * state using {@link Matcher#toMatchResult}. */ public interface MatchResult { /** * Returns the index of the first character following the text that matched * the whole regular expression. - * - * @return the character index. */ int end(); /** * Returns the index of the first character following the text that matched - * a given group. - * - * @param group - * the group, ranging from 0 to groupCount() - 1, with 0 - * representing the whole pattern. - * - * @return the character index. + * a given group. See {@link #group} for an explanation of group indexes. */ int end(int group); /** * Returns the text that matched the whole regular expression. - * - * @return the text. */ String group(); /** * Returns the text that matched a given group of the regular expression. * - * @param group - * the group, ranging from 0 to groupCount() - 1, with 0 - * representing the whole pattern. + * <p>Explicit capturing groups in the pattern are numbered left to right in order + * of their <i>opening</i> parenthesis, starting at 1. + * The special group 0 represents the entire match (as if the entire pattern is surrounded + * by an implicit capturing group). + * For example, "a((b)c)" matching "abc" would give the following groups: + * <pre> + * 0 "abc" + * 1 "bc" + * 2 "b" + * </pre> * - * @return the text that matched the group. + * <p>An optional capturing group that failed to match as part of an overall + * successful match (for example, "a(b)?c" matching "ac") returns null. + * A capturing group that matched the empty string (for example, "a(b?)c" matching "ac") + * returns the empty string. */ String group(int group); /** - * Returns the number of groups in the result, which is always equal to + * Returns the number of groups in the results, which is always equal to * the number of groups in the original regular expression. - * - * @return the number of groups. */ int groupCount(); /** - * Returns the index of the first character of the text that matched - * the whole regular expression. - * - * @return the character index. + * Returns the index of the first character of the text that matched the + * whole regular expression. */ int start(); /** * Returns the index of the first character of the text that matched a given - * group. - * - * @param group - * the group, ranging from 0 to groupCount() - 1, with 0 - * representing the whole pattern. - * - * @return the character index. + * group. See {@link #group} for an explanation of group indexes. */ int start(int group); } diff --git a/luni/src/main/java/java/util/regex/MatchResultImpl.java b/luni/src/main/java/java/util/regex/MatchResultImpl.java index b685757..6a0d948 100644 --- a/luni/src/main/java/java/util/regex/MatchResultImpl.java +++ b/luni/src/main/java/java/util/regex/MatchResultImpl.java @@ -32,7 +32,7 @@ class MatchResultImpl implements MatchResult { /** * Holds the offsets of the groups in the input text. The first two - * elements specifiy start and end of the zero group, the next two specify + * elements specify start and end of the zero group, the next two specify * group 1, and so on. */ private int[] offsets; diff --git a/luni/src/main/java/java/util/regex/Matcher.java b/luni/src/main/java/java/util/regex/Matcher.java index 02531d7..d181d45 100644 --- a/luni/src/main/java/java/util/regex/Matcher.java +++ b/luni/src/main/java/java/util/regex/Matcher.java @@ -276,8 +276,6 @@ public final class Matcher implements MatchResult { * walk through the input and replace all matches of the {@code Pattern} * with something else. * - * @param buffer - * the {@code StringBuffer} to append to. * @return the {@code StringBuffer}. * @throws IllegalStateException * if no successful match has been made. @@ -325,57 +323,12 @@ public final class Matcher implements MatchResult { /** * Returns the {@link Pattern} instance used inside this matcher. - * - * @return the {@code Pattern} instance. */ public Pattern pattern() { return pattern; } /** - * Returns the text that matched a given group of the regular expression. - * Explicit capturing groups in the pattern are numbered left to right in order - * of their <i>opening</i> parenthesis, starting at 1. - * The special group 0 represents the entire match (as if the entire pattern is surrounded - * by an implicit capturing group). - * For example, "a((b)c)" matching "abc" would give the following groups: - * <pre> - * 0 "abc" - * 1 "bc" - * 2 "b" - * </pre> - * - * <p>An optional capturing group that failed to match as part of an overall - * successful match (for example, "a(b)?c" matching "ac") returns null. - * A capturing group that matched the empty string (for example, "a(b?)c" matching "ac") - * returns the empty string. - * - * @throws IllegalStateException - * if no successful match has been made. - */ - public String group(int group) { - ensureMatch(); - int from = matchOffsets[group * 2]; - int to = matchOffsets[(group * 2) + 1]; - if (from == -1 || to == -1) { - return null; - } else { - return input.substring(from, to); - } - } - - /** - * Returns the text that matched the whole regular expression. - * - * @return the text. - * @throws IllegalStateException - * if no successful match has been made. - */ - public String group() { - return group(0); - } - - /** * Returns true if there is another match in the input, starting * from the given position. The region is ignored. * @@ -393,7 +346,7 @@ public final class Matcher implements MatchResult { } /** - * Returns the next occurrence of the {@link Pattern} in the input. If a + * Moves to the next occurrence of the pattern in the input. If a * previous match was successful, the method continues the search from the * first character following that match in the input. Otherwise it searches * either from the region start (if one has been set), or from position 0. @@ -436,45 +389,8 @@ public final class Matcher implements MatchResult { } /** - * Returns the index of the first character of the text that matched a given - * group. - * - * @param group - * the group, ranging from 0 to groupCount() - 1, with 0 - * representing the whole pattern. - * @return the character index. - * @throws IllegalStateException - * if no successful match has been made. - */ - public int start(int group) throws IllegalStateException { - ensureMatch(); - return matchOffsets[group * 2]; - } - - /** - * Returns the index of the first character following the text that matched - * a given group. - * - * @param group - * the group, ranging from 0 to groupCount() - 1, with 0 - * representing the whole pattern. - * @return the character index. - * @throws IllegalStateException - * if no successful match has been made. - */ - public int end(int group) { - ensureMatch(); - return matchOffsets[(group * 2) + 1]; - } - - /** * Returns a replacement string for the given one that has all backslashes * and dollar signs escaped. - * - * @param s - * the input string. - * @return the input string, with all backslashes and dollar signs having - * been escaped. */ public static String quoteReplacement(String s) { StringBuilder result = new StringBuilder(s.length()); @@ -489,47 +405,10 @@ public final class Matcher implements MatchResult { } /** - * Returns the index of the first character of the text that matched the - * whole regular expression. - * - * @return the character index. - * @throws IllegalStateException - * if no successful match has been made. - */ - public int start() { - return start(0); - } - - /** - * Returns the number of groups in the results, which is always equal to - * the number of groups in the original regular expression. - * - * @return the number of groups. - */ - public int groupCount() { - synchronized (this) { - return groupCountImpl(address); - } - } - - /** - * Returns the index of the first character following the text that matched - * the whole regular expression. - * - * @return the character index. - * @throws IllegalStateException - * if no successful match has been made. - */ - public int end() { - return end(0); - } - - /** * Converts the current match into a separate {@link MatchResult} instance * that is independent from this matcher. The new object is unaffected when * the state of this matcher changes. * - * @return the new {@code MatchResult}. * @throws IllegalStateException * if no successful match has been made. */ @@ -544,8 +423,6 @@ public final class Matcher implements MatchResult { * '^' and '$' meta-characters, otherwise not. Anchoring bounds are enabled * by default. * - * @param value - * the new value for anchoring bounds. * @return the {@code Matcher} itself. */ public Matcher useAnchoringBounds(boolean value) { @@ -572,8 +449,6 @@ public final class Matcher implements MatchResult { * region are subject to lookahead and lookbehind, otherwise they are not. * Transparent bounds are disabled by default. * - * @param value - * the new value for transparent bounds. * @return the {@code Matcher} itself. */ public Matcher useTransparentBounds(boolean value) { @@ -666,6 +541,78 @@ public final class Matcher implements MatchResult { " lastmatch=" + (matchFound ? group() : "") + "]"; } + /** + * {@inheritDoc} + * + * @throws IllegalStateException if no successful match has been made. + */ + public int end() { + return end(0); + } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if no successful match has been made. + */ + public int end(int group) { + ensureMatch(); + return matchOffsets[(group * 2) + 1]; + } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if no successful match has been made. + */ + public String group() { + return group(0); + } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if no successful match has been made. + */ + public String group(int group) { + ensureMatch(); + int from = matchOffsets[group * 2]; + int to = matchOffsets[(group * 2) + 1]; + if (from == -1 || to == -1) { + return null; + } else { + return input.substring(from, to); + } + } + + /** + * {@inheritDoc} + */ + public int groupCount() { + synchronized (this) { + return groupCountImpl(address); + } + } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if no successful match has been made. + */ + public int start() { + return start(0); + } + + /** + * {@inheritDoc} + * + * @throws IllegalStateException if no successful match has been made. + */ + public int start(int group) throws IllegalStateException { + ensureMatch(); + return matchOffsets[group * 2]; + } + private static native void closeImpl(long addr); private static native boolean findImpl(long addr, String s, int startIndex, int[] offsets); private static native boolean findNextImpl(long addr, String s, int[] offsets); diff --git a/luni/src/main/java/java/util/regex/Pattern.java b/luni/src/main/java/java/util/regex/Pattern.java index a33ee93..8f3fb12 100644 --- a/luni/src/main/java/java/util/regex/Pattern.java +++ b/luni/src/main/java/java/util/regex/Pattern.java @@ -183,7 +183,7 @@ import java.io.Serializable; * <tr> <td> <i>a</i>|<i>b</i> </td> <td>Either expression <i>a</i> or expression <i>b</i>.</td> </tr> * </table> * - * <a name="flags"><h3>Flags</h3></a> + * <a name="flags"></a><h3>Flags</h3> * <p><table> * <tr> <td> (?dimsux-dimsux:<i>a</i>) </td> <td>Evaluates the expression <i>a</i> with the given flags enabled/disabled.</td> </tr> * <tr> <td> (?dimsux-dimsux) </td> <td>Evaluates the rest of the pattern with the given flags enabled/disabled.</td> </tr> diff --git a/luni/src/main/java/java/util/spi/CurrencyNameProvider.java b/luni/src/main/java/java/util/spi/CurrencyNameProvider.java deleted file mode 100644 index d717aa2..0000000 --- a/luni/src/main/java/java/util/spi/CurrencyNameProvider.java +++ /dev/null @@ -1,49 +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 java.util.spi;
-
-import java.util.Locale;
-
-/**
- * This abstract class should be extended by service providers that provide
- * localized currency symbols (currency names) from currency codes.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class CurrencyNameProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected CurrencyNameProvider() {
- // do nothing
- }
-
- /**
- * Returns the localized currency symbol for the given currency code.
- *
- * @param code an ISO 4217 currency code
- * @param locale a locale
- * @return the symbol or null if there is no available symbol in the locale
- * @throws NullPointerException
- * if {@code code == null || locale == null}
- * @throws IllegalArgumentException
- * if code or locale is not in a legal format or not available
- */
- public abstract String getSymbol(String code, Locale locale);
-}
diff --git a/luni/src/main/java/java/util/spi/LocaleNameProvider.java b/luni/src/main/java/java/util/spi/LocaleNameProvider.java deleted file mode 100644 index 0d25074..0000000 --- a/luni/src/main/java/java/util/spi/LocaleNameProvider.java +++ /dev/null @@ -1,75 +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 java.util.spi;
-
-import java.util.Locale;
-
-/**
- * This abstract class should be extended by service providers that provide
- * localized locale names.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class LocaleNameProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected LocaleNameProvider() {
- // do nothing
- }
-
- /**
- * Returns the localized name for the given ISO 639 language code.
- *
- * @param languageCode an ISO 639 language code
- * @param locale a locale
- * @return the name or null if unavailable
- * @throws NullPointerException
- * if {@code code == null || locale == null}
- * @throws IllegalArgumentException
- * if code or locale is not in a legal format or not available
- */
- public abstract String getDisplayLanguage(String languageCode, Locale locale);
-
- /**
- * Returns the localized name for the given ISO 3166 country code.
- *
- * @param countryCode an ISO 3166 language code
- * @param locale a locale
- * @return the name or null if unavailable
- * @throws NullPointerException
- * if {@code code == null || locale == null}
- * @throws IllegalArgumentException
- * if code or locale is not in a legal format or not available
- */
- public abstract String getDisplayCountry(String countryCode, Locale locale);
-
- /**
- * Returns the localized name for the given variant code.
- *
- * @param variantCode a variant code
- * @param locale a locale
- * @return the name or null if unavailable
- * @throws NullPointerException
- * if {@code code == null || locale == null}
- * @throws IllegalArgumentException
- * if code or locale is not in a legal format or not available
- */
- public abstract String getDisplayVariant(String variantCode, Locale locale);
-}
diff --git a/luni/src/main/java/java/util/spi/LocaleServiceProvider.java b/luni/src/main/java/java/util/spi/LocaleServiceProvider.java deleted file mode 100644 index b1b62de..0000000 --- a/luni/src/main/java/java/util/spi/LocaleServiceProvider.java +++ /dev/null @@ -1,40 +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 java.util.spi;
-
-import java.util.Locale;
-
-/**
- * The base class for all the locale related service provider interfaces (SPIs).
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected LocaleServiceProvider() {
- // do nothing
- }
-
- /**
- * Returns all locales for which this locale service provider has localized objects or names.
- */
- public abstract Locale[] getAvailableLocales();
-}
diff --git a/luni/src/main/java/java/util/spi/TimeZoneNameProvider.java b/luni/src/main/java/java/util/spi/TimeZoneNameProvider.java deleted file mode 100644 index 533d14e..0000000 --- a/luni/src/main/java/java/util/spi/TimeZoneNameProvider.java +++ /dev/null @@ -1,51 +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 java.util.spi;
-
-import java.util.Locale;
-
-/**
- * This abstract class should be extended by service providers that provide
- * localized time zone names.
- * <p>Note that Android does not support user-supplied locale service providers.
- * @since 1.6
- * @hide
- */
-public abstract class TimeZoneNameProvider extends LocaleServiceProvider {
- /**
- * Default constructor, for use by subclasses.
- */
- protected TimeZoneNameProvider() {
- // do nothing
- }
-
- /**
- * Returns the localized name for the given time zone in the given locale.
- *
- * @param id the time zone id
- * @param daylight true to return the name for daylight saving time.
- * @param style TimeZone.LONG or TimeZone.SHORT
- * @param locale the locale
- * @return the readable time zone name, or null if it is unavailable
- * @throws NullPointerException
- * if {@code id == null || locale == null}
- * @throws IllegalArgumentException
- * if locale is not available or style is invalid
- */
- public abstract String getDisplayName(String id, boolean daylight, int style, Locale locale);
-}
diff --git a/luni/src/main/java/java/util/zip/Deflater.java b/luni/src/main/java/java/util/zip/Deflater.java index 3365031..040058b 100644 --- a/luni/src/main/java/java/util/zip/Deflater.java +++ b/luni/src/main/java/java/util/zip/Deflater.java @@ -51,7 +51,7 @@ import libcore.util.EmptyArray; * {@link DeflaterOutputStream} to handle all this for you. {@link DeflaterOutputStream} also helps * minimize memory requirements — the sample code above is very expensive. * - * <a name="compression_level"><h3>Compression levels</h3></a> + * <a name="compression_level"></a><h3>Compression levels</h3> * <p>A compression level must be {@link #DEFAULT_COMPRESSION} to compromise between speed and * compression (currently equivalent to level 6), or between 0 ({@link #NO_COMPRESSION}, where * the input is simply copied) and 9 ({@link #BEST_COMPRESSION}). Level 1 ({@link #BEST_SPEED}) @@ -347,7 +347,7 @@ public class Deflater { /** * Resets the {@code Deflater} to accept new input without affecting any - * previously made settings for the compression strategy or level. This + * previous compression strategy or level settings. This * operation <i>must</i> be called after {@link #finished} returns * true if the {@code Deflater} is to be reused. */ @@ -417,7 +417,7 @@ public class Deflater { * Sets the given <a href="#compression_level">compression level</a> * to be used when compressing data. This value must be set * prior to calling {@link #setInput setInput}. - * @exception IllegalArgumentException + * @throws IllegalArgumentException * If the compression level is invalid. */ public synchronized void setLevel(int level) { @@ -435,7 +435,7 @@ public class Deflater { * FILTERED, HUFFMAN_ONLY or DEFAULT_STRATEGY. This value must be set prior * to calling {@link #setInput setInput}. * - * @exception IllegalArgumentException + * @throws IllegalArgumentException * If the strategy specified is not one of FILTERED, * HUFFMAN_ONLY or DEFAULT_STRATEGY. */ diff --git a/luni/src/main/java/java/util/zip/DeflaterInputStream.java b/luni/src/main/java/java/util/zip/DeflaterInputStream.java index f987e39..16bf92f 100644 --- a/luni/src/main/java/java/util/zip/DeflaterInputStream.java +++ b/luni/src/main/java/java/util/zip/DeflaterInputStream.java @@ -133,17 +133,20 @@ public class DeflaterInputStream extends FilterInputStream { def.setInput(buf, 0, bytesRead); } } - int bytesDeflated = def.deflate(buf, 0, Math.min(buf.length, byteCount - count)); + int bytesDeflated = def.deflate(buffer, byteOffset + count, byteCount - count); if (bytesDeflated == -1) { break; } - System.arraycopy(buf, 0, buffer, byteOffset + count, bytesDeflated); count += bytesDeflated; } if (count == 0) { count = -1; available = false; } + + if (def.finished()) { + available = false; + } return count; } diff --git a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java index 6cce5a5..3377afd 100644 --- a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java +++ b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java @@ -188,7 +188,10 @@ public class DeflaterOutputStream extends FilterOutputStream { * Doing so may degrade compression but improve interactive behavior. */ @Override public void flush() throws IOException { - if (syncFlush) { + // Though not documented, it's illegal to call deflate with any flush param + // other than Z_FINISH after the deflater has finished. See the error checking + // at the start of the deflate function in deflate.c. + if (syncFlush && !done) { int byteCount; while ((byteCount = def.deflate(buf, 0, buf.length, Deflater.SYNC_FLUSH)) != 0) { out.write(buf, 0, byteCount); diff --git a/luni/src/main/java/java/util/zip/GZIPInputStream.java b/luni/src/main/java/java/util/zip/GZIPInputStream.java index 08599ea..1bfc496 100644 --- a/luni/src/main/java/java/util/zip/GZIPInputStream.java +++ b/luni/src/main/java/java/util/zip/GZIPInputStream.java @@ -20,9 +20,11 @@ package java.util.zip; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.io.PushbackInputStream; import java.nio.ByteOrder; import java.util.Arrays; import libcore.io.Memory; +import libcore.io.Streams; /** * The {@code GZIPInputStream} class is used to read data stored in the GZIP @@ -43,6 +45,9 @@ import libcore.io.Memory; * zis.close(); * } * </pre> + * + * <p>Note that this class ignores all remaining data at the end of the last + * GZIP member. */ public class GZIPInputStream extends InflaterInputStream { private static final int FCOMMENT = 16; @@ -53,6 +58,8 @@ public class GZIPInputStream extends InflaterInputStream { private static final int FNAME = 8; + private static final int GZIP_TRAILER_SIZE = 8; + /** * The magic header for the GZIP format. */ @@ -94,48 +101,18 @@ public class GZIPInputStream extends InflaterInputStream { */ public GZIPInputStream(InputStream is, int size) throws IOException { super(is, new Inflater(true), size); - byte[] header = new byte[10]; - readFully(header, 0, header.length); - short magic = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); - if (magic != (short) GZIP_MAGIC) { - throw new IOException(String.format("unknown format (magic number %x)", magic)); - } - int flags = header[3]; - boolean hcrc = (flags & FHCRC) != 0; - if (hcrc) { - crc.update(header, 0, header.length); - } - if ((flags & FEXTRA) != 0) { - readFully(header, 0, 2); - if (hcrc) { - crc.update(header, 0, 2); - } - int length = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff; - while (length > 0) { - int max = length > buf.length ? buf.length : length; - int result = in.read(buf, 0, max); - if (result == -1) { - throw new EOFException(); - } - if (hcrc) { - crc.update(buf, 0, result); - } - length -= result; - } - } - if ((flags & FNAME) != 0) { - readZeroTerminated(hcrc); - } - if ((flags & FCOMMENT) != 0) { - readZeroTerminated(hcrc); - } - if (hcrc) { - readFully(header, 0, 2); - short crc16 = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); - if ((short) crc.getValue() != crc16) { - throw new IOException("CRC mismatch"); + + try { + byte[] header = readHeader(is); + final short magic = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); + if (magic != (short) GZIP_MAGIC) { + throw new IOException(String.format("unknown format (magic number %x)", magic)); } - crc.reset(); + + parseGzipHeader(is, header, crc, buf); + } catch (IOException e) { + close(); // release the inflater + throw e; } } @@ -171,11 +148,109 @@ public class GZIPInputStream extends InflaterInputStream { if (eos) { verifyCrc(); + eos = maybeReadNextMember(); + if (!eos) { + crc.reset(); + inf.reset(); + eof = false; + len = 0; + } } return bytesRead; } + private boolean maybeReadNextMember() throws IOException { + // If we have any unconsumed data in the inflater buffer, we have to + // scan that first. The fact that we've reached here implies we've + // successfully consumed the GZIP trailer. + final int remaining = inf.getRemaining() - GZIP_TRAILER_SIZE; + if (remaining > 0) { + // NOTE: We make sure we create a pushback stream exactly once, + // even if the input stream contains multiple members. + // + // The push back stream we create must therefore be able to contain + // (worst case) the entire buffer even though there may be fewer bytes + // remaining when it is first created. + if (!(in instanceof PushbackInputStream)) { + in = new PushbackInputStream(in, buf.length); + } + ((PushbackInputStream) in).unread(buf, + inf.getCurrentOffset() + GZIP_TRAILER_SIZE, remaining); + } + + final byte[] buffer; + try { + buffer = readHeader(in); + } catch (EOFException eof) { + // We've reached the end of the stream and there are no more members + // to read. Note that we might also hit this if there are fewer than + // GZIP_HEADER_LENGTH bytes at the end of a member. We don't care + // because we're specified to ignore all data at the end of the last + // gzip record. + return true; + } + + final short magic = Memory.peekShort(buffer, 0, ByteOrder.LITTLE_ENDIAN); + if (magic != (short) GZIP_MAGIC) { + // Don't throw here because we've already read one valid member + // from this stream. + return true; + } + + // We've encountered the gzip magic number, so we assume there's another + // member in the stream. + parseGzipHeader(in, buffer, crc, buf); + return false; + } + + private static byte[] readHeader(InputStream in) throws IOException { + byte[] header = new byte[10]; + Streams.readFully(in, header, 0, header.length); + return header; + } + + private static void parseGzipHeader(InputStream in, byte[] header, + CRC32 crc, byte[] scratch) throws IOException { + final byte flags = header[3]; + final boolean hcrc = (flags & FHCRC) != 0; + if (hcrc) { + crc.update(header, 0, header.length); + } + if ((flags & FEXTRA) != 0) { + Streams.readFully(in, header, 0, 2); + if (hcrc) { + crc.update(header, 0, 2); + } + int length = Memory.peekShort(scratch, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff; + while (length > 0) { + int max = length > scratch.length ? scratch.length : length; + int result = in.read(scratch, 0, max); + if (result == -1) { + throw new EOFException(); + } + if (hcrc) { + crc.update(scratch, 0, result); + } + length -= result; + } + } + if ((flags & FNAME) != 0) { + readZeroTerminated(in, crc, hcrc); + } + if ((flags & FCOMMENT) != 0) { + readZeroTerminated(in, crc, hcrc); + } + if (hcrc) { + Streams.readFully(in, header, 0, 2); + short crc16 = Memory.peekShort(scratch, 0, ByteOrder.LITTLE_ENDIAN); + if ((short) crc.getValue() != crc16) { + throw new IOException("CRC mismatch"); + } + crc.reset(); + } + } + private void verifyCrc() throws IOException { // Get non-compressed bytes read by fill int size = inf.getRemaining(); @@ -184,7 +259,7 @@ public class GZIPInputStream extends InflaterInputStream { int copySize = (size > trailerSize) ? trailerSize : size; System.arraycopy(buf, len - size, b, 0, copySize); - readFully(b, copySize, trailerSize - copySize); + Streams.readFully(in, b, copySize, trailerSize - copySize); if (Memory.peekInt(b, 0, ByteOrder.LITTLE_ENDIAN) != (int) crc.getValue()) { throw new IOException("CRC mismatch"); @@ -194,20 +269,11 @@ public class GZIPInputStream extends InflaterInputStream { } } - private void readFully(byte[] buffer, int offset, int length) throws IOException { - int result; - while (length > 0) { - result = in.read(buffer, offset, length); - if (result == -1) { - throw new EOFException(); - } - offset += result; - length -= result; - } - } - - private void readZeroTerminated(boolean hcrc) throws IOException { + private static void readZeroTerminated(InputStream in, CRC32 crc, boolean hcrc) + throws IOException { int result; + // TODO: Fix these single byte reads. This method is used to consume the + // header FNAME & FCOMMENT which aren't widely used in gzip files. while ((result = in.read()) > 0) { if (hcrc) { crc.update(result); diff --git a/luni/src/main/java/java/util/zip/GZIPOutputStream.java b/luni/src/main/java/java/util/zip/GZIPOutputStream.java index 8dd907b..0111292 100644 --- a/luni/src/main/java/java/util/zip/GZIPOutputStream.java +++ b/luni/src/main/java/java/util/zip/GZIPOutputStream.java @@ -51,7 +51,7 @@ public class GZIPOutputStream extends DeflaterOutputStream { * the given stream. */ public GZIPOutputStream(OutputStream os) throws IOException { - this(os, BUF_SIZE, true); + this(os, BUF_SIZE, false); } /** @@ -65,11 +65,10 @@ public class GZIPOutputStream extends DeflaterOutputStream { /** * Constructs a new {@code GZIPOutputStream} to write data in GZIP format to - * the given stream with the given internal buffer size and - * flushing behavior (see {@link DeflaterOutputStream#flush}). + * the given stream with the given internal buffer size. */ public GZIPOutputStream(OutputStream os, int bufferSize) throws IOException { - this(os, bufferSize, true); + this(os, bufferSize, false); } /** diff --git a/luni/src/main/java/java/util/zip/Inflater.java b/luni/src/main/java/java/util/zip/Inflater.java index ee10aa7..581ed94 100644 --- a/luni/src/main/java/java/util/zip/Inflater.java +++ b/luni/src/main/java/java/util/zip/Inflater.java @@ -170,6 +170,15 @@ public class Inflater { } /** + * Returns the offset of the next byte to read in the underlying buffer. + * + * For internal use only. + */ + synchronized int getCurrentOffset() { + return inRead; + } + + /** * Returns the total number of bytes of input read by this {@code Inflater}. This * method is limited to 32 bits; use {@link #getBytesRead} instead. */ diff --git a/luni/src/main/java/java/util/zip/InflaterInputStream.java b/luni/src/main/java/java/util/zip/InflaterInputStream.java index 25b2fe8..e5ad3db 100644 --- a/luni/src/main/java/java/util/zip/InflaterInputStream.java +++ b/luni/src/main/java/java/util/zip/InflaterInputStream.java @@ -266,12 +266,7 @@ public class InflaterInputStream extends FilterInputStream { } /** - * Reset the position of the stream to the last marked position. This - * implementation overrides the supertype implementation and always throws - * an {@link IOException IOException} when called. - * - * @throws IOException - * if the method is called + * This operation is not supported and throws {@code IOException}. */ @Override public void reset() throws IOException { diff --git a/luni/src/main/java/java/util/zip/ZipEntry.java b/luni/src/main/java/java/util/zip/ZipEntry.java index f64c717..217cc3c 100644 --- a/luni/src/main/java/java/util/zip/ZipEntry.java +++ b/luni/src/main/java/java/util/zip/ZipEntry.java @@ -20,14 +20,15 @@ package java.util.zip; import java.io.IOException; import java.io.InputStream; import java.nio.ByteOrder; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; -import libcore.io.Streams; import libcore.io.BufferIterator; import libcore.io.HeapBufferIterator; +import libcore.io.Streams; /** * An entry within a zip file. @@ -54,6 +55,8 @@ public class ZipEntry implements ZipConstants, Cloneable { int nameLength = -1; long localHeaderRelOffset = -1; + long dataOffset = -1; + /** * Zip entry state: Deflated. */ @@ -64,6 +67,23 @@ public class ZipEntry implements ZipConstants, Cloneable { */ public static final int STORED = 0; + 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) { + this.name = name; + this.comment = comment; + this.crc = crc; + this.compressedSize = compressedSize; + this.size = size; + this.compressionMethod = compressionMethod; + this.time = time; + this.modDate = modDate; + this.extra = extra; + this.nameLength = nameLength; + this.localHeaderRelOffset = localHeaderRelOffset; + this.dataOffset = dataOffset; + } + /** * Constructs a new {@code ZipEntry} with the specified name. The name is actually a path, * and may contain {@code /} characters. @@ -75,9 +95,7 @@ public class ZipEntry implements ZipConstants, Cloneable { if (name == null) { throw new NullPointerException("name == null"); } - if (name.length() > 0xFFFF) { - throw new IllegalArgumentException("Name too long: " + name.length()); - } + validateStringLength("Name", name); this.name = name; } @@ -184,11 +202,8 @@ public class ZipEntry implements ZipConstants, Cloneable { this.comment = null; return; } + validateStringLength("Comment", comment); - byte[] commentBytes = comment.getBytes(StandardCharsets.UTF_8); - if (commentBytes.length > 0xffff) { - throw new IllegalArgumentException("Comment too long: " + commentBytes.length); - } this.comment = comment; } @@ -287,6 +302,17 @@ public class ZipEntry implements ZipConstants, Cloneable { } } + + /** @hide */ + public void setDataOffset(long value) { + dataOffset = value; + } + + /** @hide */ + public long getDataOffset() { + return dataOffset; + } + /** * Returns the string representation of this {@code ZipEntry}. * @@ -316,6 +342,7 @@ public class ZipEntry implements ZipConstants, Cloneable { extra = ze.extra; nameLength = ze.nameLength; localHeaderRelOffset = ze.localHeaderRelOffset; + dataOffset = ze.dataOffset; } /** @@ -344,12 +371,14 @@ public class ZipEntry implements ZipConstants, Cloneable { /* * Internal constructor. Creates a new ZipEntry by reading the * Central Directory Entry (CDE) from "in", which must be positioned - * at the CDE signature. + * at the CDE signature. If the GPBF_UTF8_FLAG is set in the CDE then + * UTF-8 is used to decode the string information, otherwise the + * defaultCharset is used. * * On exit, "in" will be positioned at the start of the next entry * in the Central Directory. */ - ZipEntry(byte[] cdeHdrBuf, InputStream cdStream) throws IOException { + ZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset) throws IOException { Streams.readFully(cdStream, cdeHdrBuf, 0, cdeHdrBuf.length); BufferIterator it = HeapBufferIterator.iterator(cdeHdrBuf, 0, cdeHdrBuf.length, @@ -367,6 +396,13 @@ public class ZipEntry implements ZipConstants, Cloneable { throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf); } + // If the GPBF_UTF8_FLAG is set then the character encoding is UTF-8 whatever the default + // provided. + Charset charset = defaultCharset; + if ((gpbf & ZipFile.GPBF_UTF8_FLAG) != 0) { + charset = StandardCharsets.UTF_8; + } + compressionMethod = it.readShort() & 0xffff; time = it.readShort() & 0xffff; modDate = it.readShort() & 0xffff; @@ -389,19 +425,17 @@ public class ZipEntry implements ZipConstants, Cloneable { if (containsNulByte(nameBytes)) { throw new ZipException("Filename contains NUL byte: " + Arrays.toString(nameBytes)); } - name = new String(nameBytes, 0, nameBytes.length, StandardCharsets.UTF_8); + name = new String(nameBytes, 0, nameBytes.length, charset); if (extraLength > 0) { extra = new byte[extraLength]; Streams.readFully(cdStream, extra, 0, extraLength); } - // The RI has always assumed UTF-8. (If GPBF_UTF8_FLAG isn't set, the encoding is - // actually IBM-437.) if (commentByteCount > 0) { byte[] commentBytes = new byte[commentByteCount]; Streams.readFully(cdStream, commentBytes, 0, commentByteCount); - comment = new String(commentBytes, 0, commentBytes.length, StandardCharsets.UTF_8); + comment = new String(commentBytes, 0, commentBytes.length, charset); } } @@ -413,4 +447,14 @@ public class ZipEntry implements ZipConstants, Cloneable { } return false; } + + private static void validateStringLength(String argument, String string) { + // This check is not perfect: the character encoding is determined when the entry is + // written out. UTF-8 is probably a worst-case: most alternatives should be single byte per + // character. + byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + if (bytes.length > 0xffff) { + throw new IllegalArgumentException(argument + " too long: " + bytes.length); + } + } } diff --git a/luni/src/main/java/java/util/zip/ZipFile.java b/luni/src/main/java/java/util/zip/ZipFile.java index c25bbc1..b44156e 100644 --- a/luni/src/main/java/java/util/zip/ZipFile.java +++ b/luni/src/main/java/java/util/zip/ZipFile.java @@ -32,6 +32,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import libcore.io.BufferIterator; import libcore.io.HeapBufferIterator; +import libcore.io.IoUtils; import libcore.io.Streams; /** @@ -108,6 +109,9 @@ public class ZipFile implements Closeable, ZipConstants { /** * Constructs a new {@code ZipFile} allowing read access to the contents of the given file. + * + * <p>UTF-8 is used to decode all comments and entry names in the file. + * * @throws ZipException if a zip error occurs. * @throws IOException if an {@code IOException} occurs. */ @@ -117,6 +121,9 @@ public class ZipFile implements Closeable, ZipConstants { /** * Constructs a new {@code ZipFile} allowing read access to the contents of the given file. + * + * <p>UTF-8 is used to decode all comments and entry names in the file. + * * @throws IOException if an IOException occurs. */ public ZipFile(String name) throws IOException { @@ -125,9 +132,11 @@ public class ZipFile implements Closeable, ZipConstants { /** * Constructs a new {@code ZipFile} allowing access to the given file. - * The {@code mode} must be either {@code OPEN_READ} or {@code OPEN_READ|OPEN_DELETE}. * - * <p>If the {@code OPEN_DELETE} flag is supplied, the file will be deleted at or before the + * <p>UTF-8 is used to decode all comments and entry names in the file. + * + * <p>The {@code mode} must be either {@code OPEN_READ} or {@code OPEN_READ|OPEN_DELETE}. + * If the {@code OPEN_DELETE} flag is supplied, the file will be deleted at or before the * time that the {@code ZipFile} is closed (the contents will remain accessible until * this {@code ZipFile} is closed); it also calls {@code File.deleteOnExit}. * @@ -148,7 +157,19 @@ public class ZipFile implements Closeable, ZipConstants { raf = new RandomAccessFile(filename, "r"); - readCentralDir(); + // Make sure to close the RandomAccessFile if reading the central directory fails. + boolean mustCloseFile = true; + try { + readCentralDir(); + + // Read succeeded so do not close the underlying RandomAccessFile. + mustCloseFile = false; + } finally { + if (mustCloseFile) { + IoUtils.closeQuietly(raf); + } + } + guard.open("close"); } @@ -357,6 +378,9 @@ public class ZipFile implements Closeable, ZipConstants { raf.seek(0); final int headerMagic = Integer.reverseBytes(raf.readInt()); + if (headerMagic == ENDSIG) { + throw new ZipException("Empty zip archive not supported"); + } if (headerMagic != LOCSIG) { throw new ZipException("Not a zip archive"); } @@ -411,7 +435,7 @@ public class ZipFile implements Closeable, ZipConstants { 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); + ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream, StandardCharsets.UTF_8); if (newEntry.localHeaderRelOffset >= centralDirOffset) { throw new ZipException("Local file header offset is after central directory"); } @@ -434,16 +458,23 @@ public class ZipFile implements Closeable, ZipConstants { * collisions.) * * <p>We could support mark/reset, but we don't currently need them. + * + * @hide */ - static class RAFStream extends InputStream { + public static class RAFStream extends InputStream { private final RandomAccessFile sharedRaf; private long endOffset; private long offset; - public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException { + + public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) { sharedRaf = raf; offset = initialOffset; - endOffset = raf.length(); + this.endOffset = endOffset; + } + + public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException { + this(raf, initialOffset, raf.length()); } @Override public int available() throws IOException { @@ -491,7 +522,8 @@ public class ZipFile implements Closeable, ZipConstants { } } - static class ZipInflaterInputStream extends InflaterInputStream { + /** @hide */ + public static class ZipInflaterInputStream extends InflaterInputStream { private final ZipEntry entry; private long bytesRead = 0; diff --git a/luni/src/main/java/java/util/zip/ZipInputStream.java b/luni/src/main/java/java/util/zip/ZipInputStream.java index 9c18f49..4c0034e 100644 --- a/luni/src/main/java/java/util/zip/ZipInputStream.java +++ b/luni/src/main/java/java/util/zip/ZipInputStream.java @@ -22,8 +22,7 @@ import java.io.InputStream; import java.io.PushbackInputStream; import java.nio.ByteOrder; import java.nio.charset.ModifiedUtf8; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import libcore.io.Memory; import libcore.io.Streams; @@ -86,12 +85,14 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants private final CRC32 crc = new CRC32(); - private byte[] nameBuf = new byte[256]; + private byte[] stringBytesBuf = new byte[256]; - private char[] charBuf = new char[256]; + private char[] stringCharBuf = new char[256]; /** * Constructs a new {@code ZipInputStream} to read zip entries from the given input stream. + * + * <p>UTF-8 is used to decode all strings in the file. */ public ZipInputStream(InputStream stream) { super(new PushbackInputStream(stream, BUF_SIZE), new Inflater(true)); @@ -125,12 +126,6 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants if (currentEntry == null) { return; } - if (currentEntry instanceof java.util.jar.JarEntry) { - Attributes temp = ((JarEntry) currentEntry).getAttributes(); - if (temp != null && temp.containsKey("hidden")) { - return; - } - } /* * The following code is careful to leave the ZipInputStream in a @@ -257,14 +252,8 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants } int extraLength = peekShort(LOCEXT - LOCVER); - if (nameLength > nameBuf.length) { - nameBuf = new byte[nameLength]; - // The bytes are modified UTF-8, so the number of chars will always be less than or - // equal to the number of bytes. It's fine if this buffer is too long. - charBuf = new char[nameLength]; - } - Streams.readFully(in, nameBuf, 0, nameLength); - currentEntry = createZipEntry(ModifiedUtf8.decode(nameBuf, charBuf, 0, nameLength)); + String name = readString(nameLength); + currentEntry = createZipEntry(name); currentEntry.time = ceLastModifiedTime; currentEntry.modDate = ceLastModifiedDate; currentEntry.setMethod(ceCompressionMethod); @@ -281,6 +270,22 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants return currentEntry; } + /** + * Reads bytes from the current stream position returning the string representation. + */ + private String readString(int byteLength) throws IOException { + if (byteLength > stringBytesBuf.length) { + stringBytesBuf = new byte[byteLength]; + } + Streams.readFully(in, stringBytesBuf, 0, byteLength); + // The number of chars will always be less than or equal to the number of bytes. It's + // fine if this buffer is too long. + if (byteLength > stringCharBuf.length) { + stringCharBuf = new char[byteLength]; + } + return ModifiedUtf8.decode(stringBytesBuf, stringCharBuf, 0, byteLength); + } + private int peekShort(int offset) { return Memory.peekShort(hdrBuf, offset, ByteOrder.LITTLE_ENDIAN) & 0xffff; } diff --git a/luni/src/main/java/java/util/zip/ZipOutputStream.java b/luni/src/main/java/java/util/zip/ZipOutputStream.java index 04de03f..8278355 100644 --- a/luni/src/main/java/java/util/zip/ZipOutputStream.java +++ b/luni/src/main/java/java/util/zip/ZipOutputStream.java @@ -85,13 +85,19 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant private final CRC32 crc = new CRC32(); - private int offset = 0, curOffset = 0, nameLength; + private int offset = 0, curOffset = 0; + /** The charset-encoded name for the current entry. */ private byte[] nameBytes; + /** The charset-encoded comment for the current entry. */ + private byte[] entryCommentBytes; + /** - * Constructs a new {@code ZipOutputStream} that writes a zip file - * to the given {@code OutputStream}. + * Constructs a new {@code ZipOutputStream} that writes a zip file to the given + * {@code OutputStream}. + * + * <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)); @@ -153,8 +159,8 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant // Update the CentralDirectory // http://www.pkware.com/documents/casestudies/APPNOTE.TXT int flags = currentEntry.getMethod() == STORED ? 0 : ZipFile.GPBF_DATA_DESCRIPTOR_FLAG; - // Since gingerbread, we always set the UTF-8 flag on individual files. - // Some tools insist that the central directory also have the UTF-8 flag. + // Since gingerbread, we always set the UTF-8 flag on individual files if appropriate. + // 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); @@ -172,19 +178,14 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant curOffset += writeLong(cDir, crc.tbytes); writeLong(cDir, crc.tbytes); } - curOffset += writeShort(cDir, nameLength); + curOffset += writeShort(cDir, nameBytes.length); if (currentEntry.extra != null) { curOffset += writeShort(cDir, currentEntry.extra.length); } else { writeShort(cDir, 0); } - String comment = currentEntry.getComment(); - byte[] commentBytes = EmptyArray.BYTE; - if (comment != null) { - commentBytes = comment.getBytes(StandardCharsets.UTF_8); - } - writeShort(cDir, commentBytes.length); // Comment length. + writeShort(cDir, entryCommentBytes.length); // Comment length. writeShort(cDir, 0); // Disk Start writeShort(cDir, 0); // Internal File Attributes writeLong(cDir, 0); // External File Attributes @@ -195,8 +196,9 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant cDir.write(currentEntry.extra); } offset += curOffset; - if (commentBytes.length > 0) { - cDir.write(commentBytes); + if (entryCommentBytes.length > 0) { + cDir.write(entryCommentBytes); + entryCommentBytes = EmptyArray.BYTE; } currentEntry = null; crc.reset(); @@ -295,9 +297,13 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant throw new ZipException("Too many entries for the zip file format's 16-bit entry count"); } nameBytes = ze.name.getBytes(StandardCharsets.UTF_8); - nameLength = nameBytes.length; - if (nameLength > 0xffff) { - throw new IllegalArgumentException("Name too long: " + nameLength + " UTF-8 bytes"); + checkSizeIsWithinShort("Name", nameBytes); + entryCommentBytes = EmptyArray.BYTE; + if (ze.comment != null) { + entryCommentBytes = ze.comment.getBytes(StandardCharsets.UTF_8); + // The comment is not written out until the entry is finished, but it is validated here + // to fail-fast. + checkSizeIsWithinShort("Comment", entryCommentBytes); } def.setLevel(compressionLevel); @@ -310,7 +316,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant // http://www.pkware.com/documents/casestudies/APPNOTE.TXT int flags = (method == STORED) ? 0 : ZipFile.GPBF_DATA_DESCRIPTOR_FLAG; // Java always outputs UTF-8 filenames. (Before Java 7, the RI didn't set this flag and used - // modified UTF-8. From Java 7, it sets this flag and uses normal UTF-8.) + // 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. @@ -331,7 +337,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant writeLong(out, 0); writeLong(out, 0); } - writeShort(out, nameLength); + writeShort(out, nameBytes.length); if (currentEntry.extra != null) { writeShort(out, currentEntry.extra.length); } else { @@ -345,18 +351,16 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant /** * Sets the comment associated with the file being written. See {@link ZipFile#getComment}. - * @throws IllegalArgumentException if the comment is >= 64 Ki UTF-8 bytes. + * @throws IllegalArgumentException if the comment is >= 64 Ki encoded bytes. */ public void setComment(String comment) { if (comment == null) { - this.commentBytes = null; + this.commentBytes = EmptyArray.BYTE; return; } byte[] newCommentBytes = comment.getBytes(StandardCharsets.UTF_8); - if (newCommentBytes.length > 0xffff) { - throw new IllegalArgumentException("Comment too long: " + newCommentBytes.length + " bytes"); - } + checkSizeIsWithinShort("Comment", newCommentBytes); this.commentBytes = newCommentBytes; } @@ -400,7 +404,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant /** * Writes data for the current entry to the underlying stream. * - * @exception IOException + * @throws IOException * If an error occurs writing to the stream */ @Override @@ -423,4 +427,11 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant throw new IOException("Stream is closed"); } } + + private void checkSizeIsWithinShort(String property, byte[] bytes) { + if (bytes.length > 0xffff) { + throw new IllegalArgumentException(property + " too long in UTF-8:" + bytes.length + + " bytes"); + } + } } diff --git a/luni/src/main/java/javax/crypto/Cipher.java b/luni/src/main/java/javax/crypto/Cipher.java index ba40b86..2e3b341 100644 --- a/luni/src/main/java/javax/crypto/Cipher.java +++ b/luni/src/main/java/javax/crypto/Cipher.java @@ -26,11 +26,15 @@ import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; +import java.security.Provider.Service; +import java.security.ProviderException; import java.security.SecureRandom; import java.security.Security; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.Locale; import java.util.Set; import org.apache.harmony.crypto.internal.NullCipherSpi; import org.apache.harmony.security.fortress.Engine; @@ -54,7 +58,7 @@ import org.apache.harmony.security.fortress.Engine; * <ul> * {@code Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");} * </ul> - * When a block cipher is requested in in stream cipher mode, the number of bits + * When a block cipher is requested in stream cipher mode, the number of bits * to be processed at a time can be optionally specified by appending it to the * mode name. e.g. <i>"AES/CFB8/NoPadding"</i>. If no number is specified, a * provider specific default value is used. @@ -98,6 +102,11 @@ public class Cipher { private int mode; + /** Items that need to be set on the Cipher instance. */ + private enum NeedToSet { + NONE, MODE, PADDING, BOTH, + }; + /** * The service name. */ @@ -108,20 +117,46 @@ public class Cipher { */ private static final Engine ENGINE = new Engine(SERVICE); + /** The attribute used for supported paddings. */ + private static final String ATTRIBUTE_PADDINGS = "SupportedPaddings"; + + /** The attribute used for supported modes. */ + private static final String ATTRIBUTE_MODES = "SupportedModes"; + /** * The provider. */ private Provider provider; /** + * The provider specified when instance created. + */ + private final Provider specifiedProvider; + + /** * The SPI implementation. */ private CipherSpi spiImpl; /** + * The SPI implementation. + */ + private final CipherSpi specifiedSpi; + + /** * The transformation. */ - private String transformation; + private final String transformation; + + /** + * The transformation split into parts. + */ + private final String[] transformParts; + + /** + * Lock held while the SPI is initializing. + */ + private final Object initLock = new Object(); private static SecureRandom secureRandom; @@ -138,19 +173,27 @@ public class Cipher { * if either cipherSpi is {@code null} or provider is {@code * null} and {@code cipherSpi} is a {@code NullCipherSpi}. */ - protected Cipher(CipherSpi cipherSpi, Provider provider, - String transformation) { + protected Cipher(CipherSpi cipherSpi, Provider provider, String transformation) { if (cipherSpi == null) { throw new NullPointerException("cipherSpi == null"); } if (!(cipherSpi instanceof NullCipherSpi) && provider == null) { throw new NullPointerException("provider == null"); } - this.provider = provider; + this.specifiedProvider = provider; + this.specifiedSpi = cipherSpi; this.transformation = transformation; - this.spiImpl = cipherSpi; + this.transformParts = null; } + private Cipher(String transformation, String[] transformParts, Provider provider) { + this.transformation = transformation; + this.transformParts = transformParts; + this.specifiedProvider = provider; + this.specifiedSpi = null; + } + + /** * Creates a new Cipher for the specified transformation. The installed * providers are searched in order for an implementation of the specified @@ -211,7 +254,8 @@ public class Cipher { } /** - * Creates a new cipher for the specified transformation. + * Creates a new cipher for the specified transformation. The + * {@code provider} supplied does not have to be registered. * * @param transformation * the name of the transformation to create a cipher for. @@ -234,8 +278,7 @@ public class Cipher { if (provider == null) { throw new IllegalArgumentException("provider == null"); } - Cipher c = getCipher(transformation, provider); - return c; + return getCipher(transformation, provider); } private static NoSuchAlgorithmException invalidTransformation(String transformation) @@ -244,91 +287,31 @@ public class Cipher { } /** - * Find appropriate Cipher according the specification rules - * - * @param transformation - * @param provider - * @return - * @throws NoSuchAlgorithmException - * @throws NoSuchPaddingException + * Create a Cipher instance but don't choose a CipherSpi until we have more + * information. */ - private static synchronized Cipher getCipher(String transformation, Provider provider) + private static Cipher getCipher(String transformation, Provider provider) throws NoSuchAlgorithmException, NoSuchPaddingException { - if (transformation == null || transformation.isEmpty()) { throw invalidTransformation(transformation); } - String[] transf = checkTransformation(transformation); - - boolean needSetPadding = false; - boolean needSetMode = false; - Object engineSpi = null; - Provider engineProvider = provider; - if (transf[1] == null && transf[2] == null) { // "algorithm" + String[] transformParts = checkTransformation(transformation); + if (tryCombinations(null, provider, transformParts) == null) { if (provider == null) { - Engine.SpiAndProvider sap = ENGINE.getInstance(transf[0], null); - engineSpi = sap.spi; - engineProvider = sap.provider; + throw new NoSuchAlgorithmException("No provider found for " + transformation); } else { - engineSpi = ENGINE.getInstance(transf[0], provider, null); + throw new NoSuchAlgorithmException("Provider " + provider.getName() + + " does not provide " + transformation); } - } else { - String[] searchOrder = { - transf[0] + "/" + transf[1] + "/" + transf[2], // "algorithm/mode/padding" - transf[0] + "/" + transf[1], // "algorithm/mode" - transf[0] + "//" + transf[2], // "algorithm//padding" - transf[0] // "algorithm" - }; - int i; - for (i = 0; i < searchOrder.length; i++) { - try { - if (provider == null) { - Engine.SpiAndProvider sap = ENGINE.getInstance(searchOrder[i], null); - engineSpi = sap.spi; - engineProvider = sap.provider; - } else { - engineSpi = ENGINE.getInstance(searchOrder[i], provider, null); - } - break; - } catch (NoSuchAlgorithmException e) { - if (i == searchOrder.length-1) { - throw new NoSuchAlgorithmException(transformation, e); - } - } - } - switch (i) { - case 1: // "algorithm/mode" - needSetPadding = true; - break; - case 2: // "algorithm//padding" - needSetMode = true; - break; - case 3: // "algorithm" - needSetPadding = true; - needSetMode = true; - } - } - if (engineSpi == null || engineProvider == null) { - throw new NoSuchAlgorithmException(transformation); } - if (!(engineSpi instanceof CipherSpi)) { - throw new NoSuchAlgorithmException(engineSpi.getClass().getName()); - } - CipherSpi cspi = (CipherSpi) engineSpi; - Cipher c = new Cipher(cspi, engineProvider, transformation); - if (needSetMode) { - c.spiImpl.engineSetMode(transf[1]); - } - if (needSetPadding) { - c.spiImpl.engineSetPadding(transf[2]); - } - return c; + return new Cipher(transformation, transformParts, provider); } - private static String[] checkTransformation(String transformation) throws NoSuchAlgorithmException { + private static String[] checkTransformation(String transformation) + throws NoSuchAlgorithmException { // ignore an extra prefix / characters such as in - // "/DES/CBC/PKCS5Paddin" http://b/3387688 + // "/DES/CBC/PKCS5Padding" http://b/3387688 if (transformation.startsWith("/")) { transformation = transformation.substring(1); } @@ -356,11 +339,163 @@ public class Cipher { } /** + * Makes sure a CipherSpi that matches this type is selected. + */ + private CipherSpi getSpi(Key key) { + if (specifiedSpi != null) { + return specifiedSpi; + } + + synchronized (initLock) { + if (spiImpl != null && key == null) { + return spiImpl; + } + + final Engine.SpiAndProvider sap = tryCombinations(key, specifiedProvider, + transformParts); + if (sap == null) { + throw new ProviderException("No provider for " + transformation); + } + + spiImpl = (CipherSpi) sap.spi; + provider = sap.provider; + + return spiImpl; + } + } + + /** + * Convenience call when the Key is not available. + */ + private CipherSpi getSpi() { + return getSpi(null); + } + + /** + * Try all combinations of mode strings: + * + * <pre> + * [cipher]/[mode]/[padding] + * [cipher]/[mode] + * [cipher]//[padding] + * [cipher] + * </pre> + */ + private static Engine.SpiAndProvider tryCombinations(Key key, Provider provider, + String[] transformParts) { + Engine.SpiAndProvider sap = null; + + if (transformParts[1] != null && transformParts[2] != null) { + sap = tryTransform(key, provider, transformParts[0] + "/" + transformParts[1] + "/" + + transformParts[2], transformParts, NeedToSet.NONE); + if (sap != null) { + return sap; + } + } + + if (transformParts[1] != null) { + sap = tryTransform(key, provider, transformParts[0] + "/" + transformParts[1], + transformParts, NeedToSet.PADDING); + if (sap != null) { + return sap; + } + } + + if (transformParts[2] != null) { + sap = tryTransform(key, provider, transformParts[0] + "//" + transformParts[2], + transformParts, NeedToSet.MODE); + if (sap != null) { + return sap; + } + } + + return tryTransform(key, provider, transformParts[0], transformParts, NeedToSet.BOTH); + } + + private static Engine.SpiAndProvider tryTransform(Key key, Provider provider, String transform, + String[] transformParts, NeedToSet type) { + if (provider != null) { + Provider.Service service = provider.getService(SERVICE, transform); + if (service == null) { + return null; + } + return tryTransformWithProvider(key, transformParts, type, service); + } + ArrayList<Provider.Service> services = ENGINE.getServices(transform); + if (services == null) { + return null; + } + for (Provider.Service service : services) { + Engine.SpiAndProvider sap = tryTransformWithProvider(key, transformParts, type, service); + if (sap != null) { + return sap; + } + } + return null; + } + + private static Engine.SpiAndProvider tryTransformWithProvider(Key key, String[] transformParts, + NeedToSet type, Provider.Service service) { + try { + if (key != null && !service.supportsParameter(key)) { + return null; + } + + /* + * Check to see if the Cipher even supports the attributes before + * trying to instantiate it. + */ + if (!matchAttribute(service, ATTRIBUTE_MODES, transformParts[1]) + || !matchAttribute(service, ATTRIBUTE_PADDINGS, transformParts[2])) { + return null; + } + + Engine.SpiAndProvider sap = ENGINE.getInstance(service, null); + if (sap.spi == null || sap.provider == null) { + return null; + } + if (!(sap.spi instanceof CipherSpi)) { + return null; + } + CipherSpi spi = (CipherSpi) sap.spi; + if (((type == NeedToSet.MODE) || (type == NeedToSet.BOTH)) + && (transformParts[1] != null)) { + spi.engineSetMode(transformParts[1]); + } + if (((type == NeedToSet.PADDING) || (type == NeedToSet.BOTH)) + && (transformParts[2] != null)) { + spi.engineSetPadding(transformParts[2]); + } + return sap; + } catch (NoSuchAlgorithmException ignored) { + } catch (NoSuchPaddingException ignored) { + } + return null; + } + + /** + * If the attribute listed exists, check that it matches the regular + * expression. + */ + private static boolean matchAttribute(Service service, String attr, String value) { + if (value == null) { + return true; + } + final String pattern = service.getAttribute(attr); + if (pattern == null) { + return true; + } + final String valueUc = value.toUpperCase(Locale.US); + return valueUc.matches(pattern.toUpperCase(Locale.US)); + } + + /** * Returns the provider of this cipher instance. * * @return the provider of this cipher instance. */ public final Provider getProvider() { + getSpi(); return provider; } @@ -382,7 +517,7 @@ public class Cipher { * @return this ciphers block size. */ public final int getBlockSize() { - return spiImpl.engineGetBlockSize(); + return getSpi().engineGetBlockSize(); } /** @@ -399,7 +534,7 @@ public class Cipher { if (mode == 0) { throw new IllegalStateException("Cipher has not yet been initialized"); } - return spiImpl.engineGetOutputSize(inputLen); + return getSpi().engineGetOutputSize(inputLen); } /** @@ -408,7 +543,7 @@ public class Cipher { * @return the <i>initialization vector</i> for this cipher instance. */ public final byte[] getIV() { - return spiImpl.engineGetIV(); + return getSpi().engineGetIV(); } /** @@ -423,7 +558,7 @@ public class Cipher { * parameters. */ public final AlgorithmParameters getParameters() { - return spiImpl.engineGetParameters(); + return getSpi().engineGetParameters(); } /** @@ -442,6 +577,13 @@ public class Cipher { } + private void checkMode(int mode) { + if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE && mode != UNWRAP_MODE + && mode != WRAP_MODE) { + throw new InvalidParameterException("Invalid mode: " + mode); + } + } + /** * Initializes this cipher instance with the specified key. * <p> @@ -516,17 +658,10 @@ public class Cipher { // FIXME InvalidKeyException // if keysize exceeds the maximum allowable keysize // (jurisdiction policy files) - spiImpl.engineInit(opmode, key, random); + getSpi(key).engineInit(opmode, key, random); mode = opmode; } - private void checkMode(int mode) { - if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE - && mode != UNWRAP_MODE && mode != WRAP_MODE) { - throw new InvalidParameterException("Invalid mode: " + mode); - } - } - /** * Initializes this cipher instance with the specified key and algorithm * parameters. @@ -613,7 +748,7 @@ public class Cipher { // FIXME InvalidAlgorithmParameterException // cryptographic strength exceed the legal limits // (jurisdiction policy files) - spiImpl.engineInit(opmode, key, params, random); + getSpi(key).engineInit(opmode, key, params, random); mode = opmode; } @@ -704,7 +839,7 @@ public class Cipher { // FIXME InvalidAlgorithmParameterException // cryptographic strength exceed the legal limits // (jurisdiction policy files) - spiImpl.engineInit(opmode, key, params, random); + getSpi(key).engineInit(opmode, key, params, random); mode = opmode; } @@ -828,7 +963,8 @@ public class Cipher { // FIXME InvalidKeyException // if keysize exceeds the maximum allowable keysize // (jurisdiction policy files) - spiImpl.engineInit(opmode, certificate.getPublicKey(), random); + final Key key = certificate.getPublicKey(); + getSpi(key).engineInit(opmode, key, random); mode = opmode; } @@ -856,7 +992,7 @@ public class Cipher { if (input.length == 0) { return null; } - return spiImpl.engineUpdate(input, 0, input.length); + return getSpi().engineUpdate(input, 0, input.length); } /** @@ -890,7 +1026,7 @@ public class Cipher { if (input.length == 0) { return null; } - return spiImpl.engineUpdate(input, inputOffset, inputLen); + return getSpi().engineUpdate(input, inputOffset, inputLen); } private static void checkInputOffsetAndCount(int inputArrayLength, @@ -986,7 +1122,7 @@ public class Cipher { if (input.length == 0) { return 0; } - return spiImpl.engineUpdate(input, inputOffset, inputLen, output, + return getSpi().engineUpdate(input, inputOffset, inputLen, output, outputOffset); } @@ -1022,7 +1158,7 @@ public class Cipher { if (input == output) { throw new IllegalArgumentException("input == output"); } - return spiImpl.engineUpdate(input, output); + return getSpi().engineUpdate(input, output); } /** @@ -1053,7 +1189,7 @@ public class Cipher { if (input.length == 0) { return; } - spiImpl.engineUpdateAAD(input, 0, input.length); + getSpi().engineUpdateAAD(input, 0, input.length); } /** @@ -1089,7 +1225,7 @@ public class Cipher { if (input.length == 0) { return; } - spiImpl.engineUpdateAAD(input, inputOffset, inputLen); + getSpi().engineUpdateAAD(input, inputOffset, inputLen); } /** @@ -1115,7 +1251,7 @@ public class Cipher { if (input == null) { throw new IllegalArgumentException("input == null"); } - spiImpl.engineUpdateAAD(input); + getSpi().engineUpdateAAD(input); } /** @@ -1139,7 +1275,7 @@ public class Cipher { if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE) { throw new IllegalStateException(); } - return spiImpl.engineDoFinal(null, 0, 0); + return getSpi().engineDoFinal(null, 0, 0); } /** @@ -1175,7 +1311,7 @@ public class Cipher { if (outputOffset < 0) { throw new IllegalArgumentException("outputOffset < 0. outputOffset=" + outputOffset); } - return spiImpl.engineDoFinal(null, 0, 0, output, outputOffset); + return getSpi().engineDoFinal(null, 0, 0, output, outputOffset); } /** @@ -1201,7 +1337,7 @@ public class Cipher { if (mode != ENCRYPT_MODE && mode != DECRYPT_MODE) { throw new IllegalStateException(); } - return spiImpl.engineDoFinal(input, 0, input.length); + return getSpi().engineDoFinal(input, 0, input.length); } /** @@ -1236,7 +1372,7 @@ public class Cipher { throw new IllegalStateException(); } checkInputOffsetAndCount(input.length, inputOffset, inputLen); - return spiImpl.engineDoFinal(input, inputOffset, inputLen); + return getSpi().engineDoFinal(input, inputOffset, inputLen); } /** @@ -1314,7 +1450,7 @@ public class Cipher { throw new IllegalStateException(); } checkInputOffsetAndCount(input.length, inputOffset, inputLen); - return spiImpl.engineDoFinal(input, inputOffset, inputLen, output, + return getSpi().engineDoFinal(input, inputOffset, inputLen, output, outputOffset); } @@ -1354,7 +1490,7 @@ public class Cipher { if (input == output) { throw new IllegalArgumentException("input == output"); } - return spiImpl.engineDoFinal(input, output); + return getSpi().engineDoFinal(input, output); } /** @@ -1376,7 +1512,7 @@ public class Cipher { if (mode != WRAP_MODE) { throw new IllegalStateException(); } - return spiImpl.engineWrap(key); + return getSpi().engineWrap(key); } /** @@ -1406,7 +1542,7 @@ public class Cipher { if (mode != UNWRAP_MODE) { throw new IllegalStateException(); } - return spiImpl.engineUnwrap(wrappedKey, wrappedKeyAlgorithm, + return getSpi().engineUnwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType); } diff --git a/luni/src/main/java/javax/crypto/CipherInputStream.java b/luni/src/main/java/javax/crypto/CipherInputStream.java index f2f59c1..3061655 100644 --- a/luni/src/main/java/javax/crypto/CipherInputStream.java +++ b/luni/src/main/java/javax/crypto/CipherInputStream.java @@ -17,6 +17,7 @@ package javax.crypto; +import java.io.BufferedInputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; @@ -34,11 +35,8 @@ import libcore.io.Streams; * CipherInputStream} tries to read the data an decrypt them before returning. */ public class CipherInputStream extends FilterInputStream { - - private static final int I_BUFFER_SIZE = 20; - private final Cipher cipher; - private final byte[] inputBuffer = new byte[I_BUFFER_SIZE]; + private final byte[] inputBuffer; private byte[] outputBuffer; private int outputIndex; // index of the first byte to return from outputBuffer private int outputLength; // count of the bytes to return from outputBuffer @@ -60,6 +58,11 @@ public class CipherInputStream extends FilterInputStream { public CipherInputStream(InputStream is, Cipher c) { super(is); this.cipher = c; + int blockSize = Math.max(c.getBlockSize(), 1); + int bufferSize = Math.max(blockSize, + BufferedInputStream.DEFAULT_BUFFER_SIZE / blockSize * blockSize); + inputBuffer = new byte[bufferSize]; + outputBuffer = new byte[bufferSize + ((blockSize > 1) ? 2 * blockSize : 0)]; } /** @@ -76,19 +79,13 @@ public class CipherInputStream extends FilterInputStream { } /** - * Reads the next byte from this cipher input stream. - * - * @return the next byte, or {@code -1} if the end of the stream is reached. - * @throws IOException - * if an error occurs. + * Attempts to fill the input buffer and process some data through the + * cipher. Returns {@code true} if output from the cipher is available to + * use. */ - @Override - public int read() throws IOException { + private boolean fillBuffer() throws IOException { if (finished) { - return (outputIndex == outputLength) ? -1 : outputBuffer[outputIndex++] & 0xFF; - } - if (outputIndex < outputLength) { - return outputBuffer[outputIndex++] & 0xFF; + return false; } outputIndex = 0; outputLength = 0; @@ -107,7 +104,7 @@ public class CipherInputStream extends FilterInputStream { throw new IOException("Error while finalizing cipher", e); } finished = true; - break; + return outputLength != 0; } try { outputLength = cipher.update(inputBuffer, 0, byteCount, outputBuffer, 0); @@ -115,7 +112,25 @@ public class CipherInputStream extends FilterInputStream { throw new AssertionError(e); // should not happen since we sized with getOutputSize } } - return read(); + return true; + } + + /** + * Reads the next byte from this cipher input stream. + * + * @return the next byte, or {@code -1} if the end of the stream is reached. + * @throws IOException + * if an error occurs. + */ + @Override + public int read() throws IOException { + if (in == null) { + throw new NullPointerException("in == null"); + } + if (outputIndex == outputLength && !fillBuffer()) { + return -1; + } + return outputBuffer[outputIndex++] & 0xFF; } /** @@ -137,18 +152,18 @@ public class CipherInputStream extends FilterInputStream { if (in == null) { throw new NullPointerException("in == null"); } - - int i; - for (i = 0; i < len; ++i) { - int b = read(); - if (b == -1) { - return (i == 0) ? -1 : i; - } - if (buf != null) { - buf[off+i] = (byte) b; - } + if (outputIndex == outputLength && !fillBuffer()) { + return -1; + } + int available = outputLength - outputIndex; + if (available < len) { + len = available; + } + if (buf != null) { + System.arraycopy(outputBuffer, outputIndex, buf, off, len); } - return i; + outputIndex += len; + return len; } @Override @@ -158,7 +173,7 @@ public class CipherInputStream extends FilterInputStream { @Override public int available() throws IOException { - return 0; + return outputLength - outputIndex; } /** diff --git a/luni/src/main/java/javax/crypto/ExemptionMechanism.java b/luni/src/main/java/javax/crypto/ExemptionMechanism.java index 8745b78..c2d42e6 100644 --- a/luni/src/main/java/javax/crypto/ExemptionMechanism.java +++ b/luni/src/main/java/javax/crypto/ExemptionMechanism.java @@ -142,6 +142,7 @@ public class ExemptionMechanism { /** * Returns a new {@code ExemptionMechanism} instance that provides the * specified exemption mechanism algorithm from the specified provider. + * The {@code provider} supplied does not have to be registered. * * @param algorithm * the name of the requested exemption mechanism. diff --git a/luni/src/main/java/javax/crypto/KeyAgreement.java b/luni/src/main/java/javax/crypto/KeyAgreement.java index 51b4cd1..abcfd0e 100644 --- a/luni/src/main/java/javax/crypto/KeyAgreement.java +++ b/luni/src/main/java/javax/crypto/KeyAgreement.java @@ -23,9 +23,11 @@ import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; +import java.security.ProviderException; import java.security.SecureRandom; import java.security.Security; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; import org.apache.harmony.security.fortress.Engine; /** @@ -35,22 +37,33 @@ import org.apache.harmony.security.fortress.Engine; */ public class KeyAgreement { + // The service name. + private static final String SERVICE = "KeyAgreement"; + // Used to access common engine functionality - private static final Engine ENGINE = new Engine("KeyAgreement"); + private static final Engine ENGINE = new Engine(SERVICE); // Store SecureRandom private static final SecureRandom RANDOM = new SecureRandom(); // Store used provider - private final Provider provider; + private Provider provider; + + // Provider that was requested during creation. + private final Provider specifiedProvider; // Store used spi implementation - private final KeyAgreementSpi spiImpl; + private KeyAgreementSpi spiImpl; // Store used algorithm name private final String algorithm; /** + * Lock held while the SPI is initializing. + */ + private final Object initLock = new Object(); + + /** * Creates a new {@code KeyAgreement} instance. * * @param keyAgreeSpi @@ -62,9 +75,9 @@ public class KeyAgreement { */ protected KeyAgreement(KeyAgreementSpi keyAgreeSpi, Provider provider, String algorithm) { - this.provider = provider; - this.algorithm = algorithm; this.spiImpl = keyAgreeSpi; + this.specifiedProvider = provider; + this.algorithm = algorithm; } /** @@ -82,6 +95,7 @@ public class KeyAgreement { * @return the provider for this {@code KeyAgreement} instance. */ public final Provider getProvider() { + getSpi(); return provider; } @@ -96,13 +110,8 @@ public class KeyAgreement { * @throws NullPointerException * if the specified algorithm is {@code null}. */ - public static final KeyAgreement getInstance(String algorithm) - throws NoSuchAlgorithmException { - if (algorithm == null) { - throw new NullPointerException("algorithm == null"); - } - Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null); - return new KeyAgreement((KeyAgreementSpi) sap.spi, sap.provider, algorithm); + public static final KeyAgreement getInstance(String algorithm) throws NoSuchAlgorithmException { + return getKeyAgreement(algorithm, null); } /** @@ -124,9 +133,8 @@ public class KeyAgreement { * @throws IllegalArgumentException * if the specified provider name is {@code null} or empty. */ - public static final KeyAgreement getInstance(String algorithm, - String provider) throws NoSuchAlgorithmException, - NoSuchProviderException { + public static final KeyAgreement getInstance(String algorithm, String provider) + throws NoSuchAlgorithmException, NoSuchProviderException { if (provider == null || provider.isEmpty()) { throw new IllegalArgumentException("Provider is null or empty"); } @@ -134,12 +142,13 @@ public class KeyAgreement { if (impProvider == null) { throw new NoSuchProviderException(provider); } - return getInstance(algorithm, impProvider); + return getKeyAgreement(algorithm, impProvider); } /** * Create a new {@code KeyAgreement} for the specified algorithm from the - * specified provider. + * specified provider. The {@code provider} supplied does not have to be + * registered. * * @param algorithm * the name of the key agreement algorithm to create. @@ -155,29 +164,108 @@ public class KeyAgreement { * @throws NullPointerException * if the specified algorithm name is {@code null}. */ - public static final KeyAgreement getInstance(String algorithm, - Provider provider) throws NoSuchAlgorithmException { + public static final KeyAgreement getInstance(String algorithm, Provider provider) + throws NoSuchAlgorithmException { if (provider == null) { throw new IllegalArgumentException("provider == null"); } + return getKeyAgreement(algorithm, provider); + } + + private static KeyAgreement getKeyAgreement(String algorithm, Provider provider) + throws NoSuchAlgorithmException { if (algorithm == null) { throw new NullPointerException("algorithm == null"); } - Object spi = ENGINE.getInstance(algorithm, provider, null); - return new KeyAgreement((KeyAgreementSpi) spi, provider, algorithm); + + if (tryAlgorithm(null, provider, algorithm) == null) { + if (provider == null) { + throw new NoSuchAlgorithmException("No provider found for " + algorithm); + } else { + throw new NoSuchAlgorithmException("Provider " + provider.getName() + + " does not provide " + algorithm); + } + } + return new KeyAgreement(null, provider, algorithm); + } + + private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) { + if (provider != null) { + Provider.Service service = provider.getService(SERVICE, algorithm); + if (service == null) { + return null; + } + return tryAlgorithmWithProvider(key, service); + } + ArrayList<Provider.Service> services = ENGINE.getServices(algorithm); + if (services == null) { + return null; + } + for (Provider.Service service : services) { + Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service); + if (sap != null) { + return sap; + } + } + return null; + } + + private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) { + try { + if (key != null && !service.supportsParameter(key)) { + return null; + } + + Engine.SpiAndProvider sap = ENGINE.getInstance(service, null); + if (sap.spi == null || sap.provider == null) { + return null; + } + if (!(sap.spi instanceof KeyAgreementSpi)) { + return null; + } + return sap; + } catch (NoSuchAlgorithmException ignored) { + } + return null; + } + + /** + * Makes sure a KeyAgreementSpi that matches this type is selected. + */ + private KeyAgreementSpi getSpi(Key key) { + synchronized (initLock) { + if (spiImpl != null && key == null) { + return spiImpl; + } + + final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm); + if (sap == null) { + throw new ProviderException("No provider for " + getAlgorithm()); + } + + spiImpl = (KeyAgreementSpi) sap.spi; + provider = sap.provider; + + return spiImpl; + } + } + + /** + * Convenience call when the Key is not available. + */ + private KeyAgreementSpi getSpi() { + return getSpi(null); } /** * Initializes this {@code KeyAgreement} with the specified key. * - * @param key - * the key to initialize this key agreement. - * @throws InvalidKeyException - * if the specified key cannot be used to initialize this key - * agreement. + * @param key the key to initialize this key agreement. + * @throws InvalidKeyException if the specified key cannot be used to + * initialize this key agreement. */ public final void init(Key key) throws InvalidKeyException { - spiImpl.engineInit(key, RANDOM);//new SecureRandom()); + getSpi(key).engineInit(key, RANDOM);//new SecureRandom()); } /** @@ -194,7 +282,7 @@ public class KeyAgreement { */ public final void init(Key key, SecureRandom random) throws InvalidKeyException { - spiImpl.engineInit(key, random); + getSpi(key).engineInit(key, random); } /** @@ -214,7 +302,7 @@ public class KeyAgreement { */ public final void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException { - spiImpl.engineInit(key, params, RANDOM);//new SecureRandom()); + getSpi(key).engineInit(key, params, RANDOM);//new SecureRandom()); } /** @@ -237,7 +325,7 @@ public class KeyAgreement { public final void init(Key key, AlgorithmParameterSpec params, SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { - spiImpl.engineInit(key, params, random); + getSpi(key).engineInit(key, params, random); } /** @@ -259,7 +347,7 @@ public class KeyAgreement { */ public final Key doPhase(Key key, boolean lastPhase) throws InvalidKeyException, IllegalStateException { - return spiImpl.engineDoPhase(key, lastPhase); + return getSpi().engineDoPhase(key, lastPhase); } /** @@ -270,7 +358,7 @@ public class KeyAgreement { * if this key agreement is not complete. */ public final byte[] generateSecret() throws IllegalStateException { - return spiImpl.engineGenerateSecret(); + return getSpi().engineGenerateSecret(); } /** @@ -289,7 +377,7 @@ public class KeyAgreement { */ public final int generateSecret(byte[] sharedSecret, int offset) throws IllegalStateException, ShortBufferException { - return spiImpl.engineGenerateSecret(sharedSecret, offset); + return getSpi().engineGenerateSecret(sharedSecret, offset); } /** @@ -311,7 +399,7 @@ public class KeyAgreement { public final SecretKey generateSecret(String algorithm) throws IllegalStateException, NoSuchAlgorithmException, InvalidKeyException { - return spiImpl.engineGenerateSecret(algorithm); + return getSpi().engineGenerateSecret(algorithm); } } diff --git a/luni/src/main/java/javax/crypto/KeyGenerator.java b/luni/src/main/java/javax/crypto/KeyGenerator.java index 606998a..fc409da 100644 --- a/luni/src/main/java/javax/crypto/KeyGenerator.java +++ b/luni/src/main/java/javax/crypto/KeyGenerator.java @@ -137,7 +137,8 @@ public class KeyGenerator { /** * Creates a new {@code KeyGenerator} instance that provides the specified - * key algorithm from the specified provider. + * key algorithm from the specified provider. The {@code provider} + * supplied does not have to be registered. * * @param algorithm * the name of the requested key algorithm. diff --git a/luni/src/main/java/javax/crypto/Mac.java b/luni/src/main/java/javax/crypto/Mac.java index c208456..5a73dc5 100644 --- a/luni/src/main/java/javax/crypto/Mac.java +++ b/luni/src/main/java/javax/crypto/Mac.java @@ -24,8 +24,10 @@ import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; +import java.security.ProviderException; import java.security.Security; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; import org.apache.harmony.security.fortress.Engine; @@ -35,18 +37,29 @@ import org.apache.harmony.security.fortress.Engine; */ public class Mac implements Cloneable { + // The service name. + private static final String SERVICE = "Mac"; + //Used to access common engine functionality - private static final Engine ENGINE = new Engine("Mac"); + private static final Engine ENGINE = new Engine(SERVICE); // Store used provider - private final Provider provider; + private Provider provider; + + // Provider that was requested during creation. + private final Provider specifiedProvider; // Store used spi implementation - private final MacSpi spiImpl; + private MacSpi spiImpl; // Store used algorithm name private final String algorithm; + /** + * Lock held while the SPI is initializing. + */ + private final Object initLock = new Object(); + // Store Mac state (initialized or not initialized) private boolean isInitMac; @@ -61,7 +74,7 @@ public class Mac implements Cloneable { * the name of the MAC algorithm. */ protected Mac(MacSpi macSpi, Provider provider, String algorithm) { - this.provider = provider; + this.specifiedProvider = provider; this.algorithm = algorithm; this.spiImpl = macSpi; this.isInitMac = false; @@ -82,6 +95,7 @@ public class Mac implements Cloneable { * @return the provider of this {@code Mac} instance. */ public final Provider getProvider() { + getSpi(); return provider; } @@ -100,11 +114,7 @@ public class Mac implements Cloneable { */ public static final Mac getInstance(String algorithm) throws NoSuchAlgorithmException { - if (algorithm == null) { - throw new NullPointerException("algorithm == null"); - } - Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null); - return new Mac((MacSpi) sap.spi, sap.provider, algorithm); + return getMac(algorithm, null); } /** @@ -136,12 +146,13 @@ public class Mac implements Cloneable { if (impProvider == null) { throw new NoSuchProviderException(provider); } - return getInstance(algorithm, impProvider); + return getMac(algorithm, impProvider); } /** * Creates a new {@code Mac} instance that provides the specified MAC - * algorithm from the specified provider. + * algorithm from the specified provider. The {@code provider} supplied + * does not have to be registered. * * @param algorithm * the name of the requested MAC algorithm. @@ -162,11 +173,102 @@ public class Mac implements Cloneable { if (provider == null) { throw new IllegalArgumentException("provider == null"); } + return getMac(algorithm, provider); + } + + private static Mac getMac(String algorithm, Provider provider) + throws NoSuchAlgorithmException { if (algorithm == null) { throw new NullPointerException("algorithm == null"); } - Object spi = ENGINE.getInstance(algorithm, provider, null); - return new Mac((MacSpi) spi, provider, algorithm); + + if (tryAlgorithm(null, provider, algorithm) == null) { + if (provider == null) { + throw new NoSuchAlgorithmException("No provider found for " + algorithm); + } else { + throw new NoSuchAlgorithmException("Provider " + provider.getName() + + " does not provide " + algorithm); + } + } + return new Mac(null, provider, algorithm); + } + + private static Engine.SpiAndProvider tryAlgorithm(Key key, Provider provider, String algorithm) { + if (provider != null) { + Provider.Service service = provider.getService(SERVICE, algorithm); + if (service == null) { + return null; + } + return tryAlgorithmWithProvider(key, service); + } + ArrayList<Provider.Service> services = ENGINE.getServices(algorithm); + if (services == null) { + return null; + } + for (Provider.Service service : services) { + Engine.SpiAndProvider sap = tryAlgorithmWithProvider(key, service); + if (sap != null) { + return sap; + } + } + return null; + } + + private static Engine.SpiAndProvider tryAlgorithmWithProvider(Key key, Provider.Service service) { + try { + if (key != null && !service.supportsParameter(key)) { + return null; + } + + Engine.SpiAndProvider sap = ENGINE.getInstance(service, null); + if (sap.spi == null || sap.provider == null) { + return null; + } + if (!(sap.spi instanceof MacSpi)) { + return null; + } + return sap; + } catch (NoSuchAlgorithmException ignored) { + } + return null; + } + + /** + * Makes sure a MacSpi that matches this type is selected. + */ + private MacSpi getSpi(Key key) { + synchronized (initLock) { + if (spiImpl != null && provider != null && key == null) { + return spiImpl; + } + + if (algorithm == null) { + return null; + } + + final Engine.SpiAndProvider sap = tryAlgorithm(key, specifiedProvider, algorithm); + if (sap == null) { + throw new ProviderException("No provider for " + getAlgorithm()); + } + + /* + * Set our Spi if we've never been initialized or if we have the Spi + * specified and have a null provider. + */ + if (spiImpl == null || provider != null) { + spiImpl = (MacSpi) sap.spi; + } + provider = sap.provider; + + return spiImpl; + } + } + + /** + * Convenience call when the Key is not available. + */ + private MacSpi getSpi() { + return getSpi(null); } /** @@ -175,7 +277,7 @@ public class Mac implements Cloneable { * @return the length of this MAC (in bytes). */ public final int getMacLength() { - return spiImpl.engineGetMacLength(); + return getSpi().engineGetMacLength(); } /** @@ -198,7 +300,7 @@ public class Mac implements Cloneable { if (key == null) { throw new InvalidKeyException("key == null"); } - spiImpl.engineInit(key, params); + getSpi(key).engineInit(key, params); isInitMac = true; } @@ -219,7 +321,7 @@ public class Mac implements Cloneable { throw new InvalidKeyException("key == null"); } try { - spiImpl.engineInit(key, null); + getSpi(key).engineInit(key, null); isInitMac = true; } catch (InvalidAlgorithmParameterException e) { throw new RuntimeException(e); @@ -238,7 +340,7 @@ public class Mac implements Cloneable { if (!isInitMac) { throw new IllegalStateException(); } - spiImpl.engineUpdate(input); + getSpi().engineUpdate(input); } /** @@ -269,7 +371,7 @@ public class Mac implements Cloneable { + " input.length=" + input.length + " offset=" + offset + ", len=" + len); } - spiImpl.engineUpdate(input, offset, len); + getSpi().engineUpdate(input, offset, len); } /** @@ -285,7 +387,7 @@ public class Mac implements Cloneable { throw new IllegalStateException(); } if (input != null) { - spiImpl.engineUpdate(input, 0, input.length); + getSpi().engineUpdate(input, 0, input.length); } } @@ -304,7 +406,7 @@ public class Mac implements Cloneable { throw new IllegalStateException(); } if (input != null) { - spiImpl.engineUpdate(input); + getSpi().engineUpdate(input); } else { throw new IllegalArgumentException("input == null"); } @@ -326,7 +428,7 @@ public class Mac implements Cloneable { if (!isInitMac) { throw new IllegalStateException(); } - return spiImpl.engineDoFinal(); + return getSpi().engineDoFinal(); } /** @@ -361,11 +463,12 @@ public class Mac implements Cloneable { if ((outOffset < 0) || (outOffset >= output.length)) { throw new ShortBufferException("Incorrect outOffset: " + outOffset); } - int t = spiImpl.engineGetMacLength(); + MacSpi spi = getSpi(); + int t = spi.engineGetMacLength(); if (t > (output.length - outOffset)) { throw new ShortBufferException("Output buffer is short. Needed " + t + " bytes."); } - byte[] result = spiImpl.engineDoFinal(); + byte[] result = spi.engineDoFinal(); System.arraycopy(result, 0, output, outOffset, result.length); } @@ -389,10 +492,11 @@ public class Mac implements Cloneable { if (!isInitMac) { throw new IllegalStateException(); } + MacSpi spi = getSpi(); if (input != null) { - spiImpl.engineUpdate(input, 0, input.length); + spi.engineUpdate(input, 0, input.length); } - return spiImpl.engineDoFinal(); + return spi.engineDoFinal(); } /** @@ -403,7 +507,7 @@ public class Mac implements Cloneable { * initialized with different parameters. */ public final void reset() { - spiImpl.engineReset(); + getSpi().engineReset(); } /** @@ -415,7 +519,11 @@ public class Mac implements Cloneable { */ @Override public final Object clone() throws CloneNotSupportedException { - MacSpi newSpiImpl = (MacSpi)spiImpl.clone(); + MacSpi newSpiImpl = null; + final MacSpi spi = getSpi(); + if (spi != null) { + newSpiImpl = (MacSpi) spi.clone(); + } Mac mac = new Mac(newSpiImpl, this.provider, this.algorithm); mac.isInitMac = this.isInitMac; return mac; diff --git a/luni/src/main/java/javax/crypto/SealedObject.java b/luni/src/main/java/javax/crypto/SealedObject.java index cfb970b..4b91184 100644 --- a/luni/src/main/java/javax/crypto/SealedObject.java +++ b/luni/src/main/java/javax/crypto/SealedObject.java @@ -19,6 +19,7 @@ package javax.crypto; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -29,6 +30,7 @@ import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import libcore.io.IoUtils; /** * A {@code SealedObject} is a wrapper around a {@code serializable} object @@ -57,14 +59,21 @@ public class SealedObject implements Serializable { private String paramsAlg; private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { - // We do unshared reads here to ensure we have our own clones of the byte[]s. - encodedParams = (byte[]) s.readUnshared(); - encryptedContent = (byte[]) s.readUnshared(); - // These are regular shared reads because the algorithms used by a given stream are - // almost certain to the be same for each object, and String is immutable anyway, - // so there's no security concern about sharing. - sealAlg = (String) s.readObject(); - paramsAlg = (String) s.readObject(); + // This implementation is based on the latest recommendations for safe deserialization at + // the time of writing. See the Serialization spec section A.6. + ObjectInputStream.GetField fields = s.readFields(); + + // The mutable byte arrays are cloned and the immutable strings are not. + this.encodedParams = getSafeCopy(fields, "encodedParams"); + this.encryptedContent = getSafeCopy(fields, "encryptedContent"); + this.paramsAlg = (String) fields.get("paramsAlg", null); + this.sealAlg = (String) fields.get("sealAlg", null); + } + + private static byte[] getSafeCopy(ObjectInputStream.GetField fields, String fieldName) + throws IOException { + byte[] fieldValue = (byte[]) fields.get(fieldName, null); + return fieldValue != null ? fieldValue.clone() : null; } /** @@ -87,13 +96,14 @@ public class SealedObject implements Serializable { * if the cipher is {@code null}. */ public SealedObject(Serializable object, Cipher c) - throws IOException, IllegalBlockSizeException { + throws IOException, IllegalBlockSizeException { if (c == null) { throw new NullPointerException("c == null"); } + ObjectOutputStream oos = null; try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos); + oos = new ObjectOutputStream(bos); oos.writeObject(object); oos.flush(); AlgorithmParameters ap = c.getParameters(); @@ -105,6 +115,8 @@ public class SealedObject implements Serializable { // should be never thrown because the cipher // should be initialized for encryption throw new IOException(e.toString()); + } finally { + IoUtils.closeQuietly(oos); } } @@ -119,8 +131,10 @@ public class SealedObject implements Serializable { if (so == null) { throw new NullPointerException("so == null"); } - this.encryptedContent = so.encryptedContent; - this.encodedParams = so.encodedParams; + // For safety: clone the mutable arrays so that each object has its own independent copy of + // the data. + this.encryptedContent = so.encryptedContent != null ? so.encryptedContent.clone() : null; + this.encodedParams = so.encodedParams != null ? so.encodedParams.clone() : null; this.sealAlg = so.sealAlg; this.paramsAlg = so.paramsAlg; } @@ -158,18 +172,14 @@ public class SealedObject implements Serializable { try { Cipher cipher = Cipher.getInstance(sealAlg); if ((paramsAlg != null) && (paramsAlg.length() != 0)) { - AlgorithmParameters params = - AlgorithmParameters.getInstance(paramsAlg); + AlgorithmParameters params = AlgorithmParameters.getInstance(paramsAlg); params.init(encodedParams); cipher.init(Cipher.DECRYPT_MODE, key, params); } else { cipher.init(Cipher.DECRYPT_MODE, key); } byte[] serialized = cipher.doFinal(encryptedContent); - ObjectInputStream ois = - new ObjectInputStream( - new ByteArrayInputStream(serialized)); - return ois.readObject(); + return readSerialized(serialized); } catch (NoSuchPaddingException e) { // should not be thrown because cipher text was made // with existing padding @@ -186,7 +196,7 @@ public class SealedObject implements Serializable { // should not be thrown because the cipher text // was correctly made throw new NoSuchAlgorithmException(e.toString()); - } catch (IllegalStateException e) { + } catch (IllegalStateException e) { // should never be thrown because cipher is initialized throw new NoSuchAlgorithmException(e.toString()); } @@ -217,10 +227,7 @@ public class SealedObject implements Serializable { throw new NullPointerException("c == null"); } byte[] serialized = c.doFinal(encryptedContent); - ObjectInputStream ois = - new ObjectInputStream( - new ByteArrayInputStream(serialized)); - return ois.readObject(); + return readSerialized(serialized); } /** @@ -253,18 +260,14 @@ public class SealedObject implements Serializable { try { Cipher cipher = Cipher.getInstance(sealAlg, provider); if ((paramsAlg != null) && (paramsAlg.length() != 0)) { - AlgorithmParameters params = - AlgorithmParameters.getInstance(paramsAlg); + AlgorithmParameters params = AlgorithmParameters.getInstance(paramsAlg); params.init(encodedParams); cipher.init(Cipher.DECRYPT_MODE, key, params); } else { cipher.init(Cipher.DECRYPT_MODE, key); } byte[] serialized = cipher.doFinal(encryptedContent); - ObjectInputStream ois = - new ObjectInputStream( - new ByteArrayInputStream(serialized)); - return ois.readObject(); + return readSerialized(serialized); } catch (NoSuchPaddingException e) { // should not be thrown because cipher text was made // with existing padding @@ -286,4 +289,15 @@ public class SealedObject implements Serializable { throw new NoSuchAlgorithmException(e.toString()); } } + + private static Object readSerialized(byte[] serialized) + throws IOException, ClassNotFoundException { + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new ByteArrayInputStream(serialized)); + return ois.readObject(); + } finally { + IoUtils.closeQuietly(ois); + } + } } diff --git a/luni/src/main/java/javax/crypto/SecretKeyFactory.java b/luni/src/main/java/javax/crypto/SecretKeyFactory.java index 8ab3eb8..9298b8e 100644 --- a/luni/src/main/java/javax/crypto/SecretKeyFactory.java +++ b/luni/src/main/java/javax/crypto/SecretKeyFactory.java @@ -143,7 +143,8 @@ public class SecretKeyFactory { /** * Creates a new {@code SecretKeyFactory} instance for the specified key - * algorithm from the specified provider. + * algorithm from the specified provider. The {@code provider} supplied + * does not have to be registered. * * @param algorithm * the name of the key algorithm. diff --git a/luni/src/main/java/javax/net/ssl/SSLContext.java b/luni/src/main/java/javax/net/ssl/SSLContext.java index a59f301..efc1947 100644 --- a/luni/src/main/java/javax/net/ssl/SSLContext.java +++ b/luni/src/main/java/javax/net/ssl/SSLContext.java @@ -82,6 +82,46 @@ public class SSLContext { /** * Creates a new {@code SSLContext} instance for the specified protocol. * + * <p>The following protocols are supported: + * <table> + * <thead> + * <tr> + * <th>Protocol</th> + * <th>API Levels</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>Default</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>SSL</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>SSLv3</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>TLSv1</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>TLSv1.1</td> + * <td>16+</td> + * </tr> + * <tr> + * <td>TLSv1.2</td> + * <td>16+</td> + * </tr> + * </tbody> + * </table> + * * @param protocol * the requested protocol to create a context for. * @return the created {@code SSLContext} instance. @@ -103,6 +143,79 @@ public class SSLContext { * Creates a new {@code SSLContext} instance for the specified protocol from * the specified provider. * + * <p>The following combinations are supported: + * <table> + * <thead> + * <tr> + * <th>Protocol</th> + * <th>Provider</th> + * <th>API Levels</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>Default</td> + * <td>AndroidOpenSSL</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>SSL</td> + * <td>AndroidOpenSSL</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>SSL</td> + * <td>HarmonyJSSE</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSLv3</td> + * <td>AndroidOpenSSL</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>SSLv3</td> + * <td>HarmonyJSSE</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>TLS</td> + * <td>AndroidOpenSSL</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS</td> + * <td>HarmonyJSSE</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>TLSv1</td> + * <td>AndroidOpenSSL</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLSv1</td> + * <td>HarmonyJSSE</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>TLSv1.1</td> + * <td>AndroidOpenSSL</td> + * <td>16+</td> + * </tr> + * <tr> + * <td>TLSv1.2</td> + * <td>AndroidOpenSSL</td> + * <td>16+</td> + * </tr> + * </tbody> + * </table> + * + * <p><strong>NOTE:</strong> The best practice is to rely on platform + * defaults rather than explicitly specify a provider. + * {@link #getDefault()} and {@link #getInstance(String)} are normally + * preferred over this method. + * * @param protocol * the requested protocol to create a context for. * @param provider @@ -201,16 +314,33 @@ public class SSLContext { } /** - * Initializes this {@code SSLContext} instance. All of the arguments are - * optional, and the security providers will be searched for the required - * implementations of the needed algorithms. + * Initializes this {@code SSLContext} instance. Three aspects of the context can be configured + * during initialization: + * <ul> + * <li>Providers of key material for key exchange and peer authentication + * ({@link KeyManager} instances),</li> + * <li>Providers of trust decisions about peers ({@link TrustManager} instances), + * </li> + * <li>Provider of randomness ({@link SecureRandom} instance).</li> + * </ul> + * + * <p>For each type of {@code KeyManager} or {@code TrustManager} used by this context, only the + * first matching instance from {@code km} or {@code tm} will be used. For example, only the + * first instance of {@link X509TrustManager} from {@code tm} will be used. + * + * <p>For any parameter set to {@code null} defaults will be used. In that case, the installed + * security providers will be searched for the highest priority implementation of the required + * primitives. For {@code km} and {@code tm}, the highest priority implementation + * of {@link KeyManagerFactory} and {@link TrustManagerFactory} will be used to obtain the + * required types of {@code KeyManager} and {@code TrustManager}. For {@code sr}, the default + * {@code SecureRandom} implementation will be used. * * @param km - * the key sources or {@code null}. + * the key sources or {@code null} for default. * @param tm - * the trust decision sources or {@code null}. + * the trust decision sources or {@code null} for default. * @param sr - * the randomness source or {@code null.} + * the randomness source or {@code null} for default. * @throws KeyManagementException * if initializing this instance fails. */ diff --git a/luni/src/main/java/javax/net/ssl/SSLEngine.java b/luni/src/main/java/javax/net/ssl/SSLEngine.java index a6c9946..cbf02ac 100644 --- a/luni/src/main/java/javax/net/ssl/SSLEngine.java +++ b/luni/src/main/java/javax/net/ssl/SSLEngine.java @@ -24,6 +24,629 @@ import java.nio.ByteBuffer; * protocols. It includes the setup, handshake, and encrypt/decrypt * functionality needed to create a secure connection. * + * <h3>Default configuration</h3> + * <p>{@code SSLEngine} instances obtained from default {@link SSLContext} are configured as + * follows: + * + * <h4>Protocols</h4> + * <table> + * <thead> + * <tr> + * <th>Protocol</th> + * <th>Supported (API Levels)</th> + * <th>Enabled by default (API Levels)</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>SSLv3</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>TLSv1</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>TLSv1.1</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLSv1.2</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * </tbody> + * </table> + * + * <h4>Cipher suites</h4> + * <table> + * <thead> + * <tr> + * <th>Cipher suite</th> + * <th>Supported (API Levels)</th> + * <th>Enabled by default (API Levels)</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_RSA_WITH_DES_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_DH_anon_EXPORT_WITH_RC4_40_MD5</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_DH_anon_WITH_3DES_EDE_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_DH_anon_WITH_DES_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_DH_anon_WITH_RC4_128_MD5</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_EXPORT_WITH_RC4_40_MD5</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_DES_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_NULL_MD5</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_NULL_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_RC4_128_MD5</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_RC4_128_SHA</td> + * <td>9+</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td> + * <td>9+</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</td> + * <td>9+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_DES_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA</td> + * <td>9+</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA</td> + * <td>9+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_DES_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_DSS_WITH_DES_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_RSA_WITH_DES_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_3DES_EDE_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_128_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_256_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_256_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_DES_CBC_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_NULL_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_RC4_128_SHA</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_NULL_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_RC4_128_SHA</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_NULL_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_RC4_128_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_NULL_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_RC4_128_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_AES_128_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_AES_256_CBC_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_NULL_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_RC4_128_SHA</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_EMPTY_RENEGOTIATION_INFO_SCSV</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_FALLBACK_SCSV</td> + * <td>21+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA</td> + * <td>21+</td> + * <td>21+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA</td> + * <td>21+</td> + * <td>21+</td> + * </tr> + * <tr> + * <td>TLS_NULL_WITH_NULL_NULL</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_PSK_WITH_3DES_EDE_CBC_SHA</td> + * <td>21+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_PSK_WITH_AES_128_CBC_SHA</td> + * <td>21+</td> + * <td>21+</td> + * </tr> + * <tr> + * <td>TLS_PSK_WITH_AES_256_CBC_SHA</td> + * <td>21+</td> + * <td>21+</td> + * </tr> + * <tr> + * <td>TLS_PSK_WITH_RC4_128_SHA</td> + * <td>21+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_128_CBC_SHA</td> + * <td>9+</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_256_CBC_SHA</td> + * <td>9+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_256_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_DES_CBC_SHA</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_NULL_MD5</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_NULL_SHA</td> + * <td>1-8</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_NULL_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * </tbody> + * </table> + * + * <p><em>NOTE</em>: PSK cipher suites are enabled by default only if the {@code SSLContext} through + * which the engine was created has been initialized with a {@code PSKKeyManager}. + * * @since 1.5 */ public abstract class SSLEngine { diff --git a/luni/src/main/java/javax/net/ssl/SSLEngineResult.java b/luni/src/main/java/javax/net/ssl/SSLEngineResult.java index 8a98831..3360832 100644 --- a/luni/src/main/java/javax/net/ssl/SSLEngineResult.java +++ b/luni/src/main/java/javax/net/ssl/SSLEngineResult.java @@ -110,16 +110,16 @@ public class SSLEngineResult { public SSLEngineResult(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus handshakeStatus, int bytesConsumed, int bytesProduced) { if (status == null) { - throw new IllegalArgumentException("status is null"); + throw new IllegalArgumentException("status == null"); } if (handshakeStatus == null) { - throw new IllegalArgumentException("handshakeStatus is null"); + throw new IllegalArgumentException("handshakeStatus == null"); } if (bytesConsumed < 0) { - throw new IllegalArgumentException("bytesConsumed is negative"); + throw new IllegalArgumentException("bytesConsumed < 0: " + bytesConsumed); } if (bytesProduced < 0) { - throw new IllegalArgumentException("bytesProduced is negative"); + throw new IllegalArgumentException("bytesProduced < 0: " + bytesProduced); } this.status = status; this.handshakeStatus = handshakeStatus; diff --git a/luni/src/main/java/javax/net/ssl/SSLParameters.java b/luni/src/main/java/javax/net/ssl/SSLParameters.java index 6694ef2..054abe2 100644 --- a/luni/src/main/java/javax/net/ssl/SSLParameters.java +++ b/luni/src/main/java/javax/net/ssl/SSLParameters.java @@ -27,6 +27,7 @@ public class SSLParameters { private String[] protocols; private boolean needClientAuth; private boolean wantClientAuth; + private String endpointIdentificationAlgorithm; /** * The default SSLParameters constructor. Cipher suites and diff --git a/luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java b/luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java index cce72cd..03b8828 100644 --- a/luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java +++ b/luni/src/main/java/javax/net/ssl/SSLServerSocketFactory.java @@ -20,6 +20,7 @@ package javax.net.ssl; import java.security.NoSuchAlgorithmException; import java.security.Security; import javax.net.ServerSocketFactory; +import org.apache.harmony.security.fortress.Services; /** * The factory for SSL server sockets. @@ -32,6 +33,8 @@ public abstract class SSLServerSocketFactory extends ServerSocketFactory { private static String defaultName; + private static int lastCacheVersion = -1; + /** * Returns the default {@code SSLServerSocketFactory} instance. The default * implementation is defined by the security property @@ -40,6 +43,12 @@ public abstract class SSLServerSocketFactory extends ServerSocketFactory { * @return the default {@code SSLServerSocketFactory} instance. */ public static synchronized ServerSocketFactory getDefault() { + int newCacheVersion = Services.getCacheVersion(); + if (lastCacheVersion != newCacheVersion) { + defaultServerSocketFactory = null; + defaultName = null; + lastCacheVersion = newCacheVersion; + } if (defaultServerSocketFactory != null) { return defaultServerSocketFactory; } diff --git a/luni/src/main/java/javax/net/ssl/SSLSocket.java b/luni/src/main/java/javax/net/ssl/SSLSocket.java index 5049f81..dc406e1 100644 --- a/luni/src/main/java/javax/net/ssl/SSLSocket.java +++ b/luni/src/main/java/javax/net/ssl/SSLSocket.java @@ -25,6 +25,715 @@ import java.net.UnknownHostException; /** * The extension of {@code Socket} providing secure protocols like SSL (Secure * Sockets Layer) or TLS (Transport Layer Security). + * + * <h3>Default configuration</h3> + * <p>{@code SSLSocket} instances obtained from default {@link SSLSocketFactory}, + * {@link SSLServerSocketFactory}, and {@link SSLContext} are configured as follows: + * + * <h4>Protocols</h4> + * + * <p>Client socket: + * <table> + * <thead> + * <tr> + * <th>Protocol</th> + * <th>Supported (API Levels)</th> + * <th>Enabled by default (API Levels)</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>SSLv3</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>TLSv1</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>TLSv1.1</td> + * <td>16+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLSv1.2</td> + * <td>16+</td> + * <td>20+</td> + * </tr> + * </tbody> + * </table> + * + * <p>Server socket: + * <table> + * <thead> + * <tr> + * <th>Protocol</th> + * <th>Supported (API Levels)</th> + * <th>Enabled by default (API Levels)</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>SSLv3</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>TLSv1</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>TLSv1.1</td> + * <td>16+</td> + * <td>16+</td> + * </tr> + * <tr> + * <td>TLSv1.2</td> + * <td>16+</td> + * <td>16+</td> + * </tr> + * </tbody> + * </table> + * + * <h4>Cipher suites</h4> + * + * <p>Methods that operate with cipher suite names (for example, + * {@link #getSupportedCipherSuites() getSupportedCipherSuites}, + * {@link #setEnabledCipherSuites(String[]) setEnabledCipherSuites}) have used + * standard names for cipher suites since API Level 9, as listed in the table + * below. Prior to API Level 9, non-standard (OpenSSL) names had been used (see + * the table following this table). + * <table> + * <thead> + * <tr> + * <th>Cipher suite</th> + * <th>Supported (API Levels)</th> + * <th>Enabled by default (API Levels)</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DHE_RSA_WITH_DES_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_DH_anon_EXPORT_WITH_RC4_40_MD5</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_DH_anon_WITH_3DES_EDE_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_DH_anon_WITH_DES_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_DH_anon_WITH_RC4_128_MD5</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_EXPORT_WITH_RC4_40_MD5</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_DES_CBC_SHA</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_NULL_MD5</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_NULL_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_RC4_128_MD5</td> + * <td>9+</td> + * <td>9-19</td> + * </tr> + * <tr> + * <td>SSL_RSA_WITH_RC4_128_SHA</td> + * <td>9+</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td> + * <td>9+</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA</td> + * <td>9+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_256_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_DSS_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA</td> + * <td>9+</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA</td> + * <td>9+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DHE_RSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_128_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_256_CBC_SHA</td> + * <td>9+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_256_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_DH_anon_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA</td> + * <td>11+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA</td> + * <td>11+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_NULL_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_ECDSA_WITH_RC4_128_SHA</td> + * <td>11+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA</td> + * <td>21+</td> + * <td>21+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA</td> + * <td>21+</td> + * <td>21+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA</td> + * <td>11+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA</td> + * <td>11+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_NULL_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDHE_RSA_WITH_RC4_128_SHA</td> + * <td>11+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_NULL_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_ECDSA_WITH_RC4_128_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_NULL_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_RSA_WITH_RC4_128_SHA</td> + * <td>11+</td> + * <td>11-19</td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_AES_128_CBC_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_AES_256_CBC_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_NULL_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_ECDH_anon_WITH_RC4_128_SHA</td> + * <td>11+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_EMPTY_RENEGOTIATION_INFO_SCSV</td> + * <td>11+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_FALLBACK_SCSV</td> + * <td>21+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_PSK_WITH_3DES_EDE_CBC_SHA</td> + * <td>21+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_PSK_WITH_AES_128_CBC_SHA</td> + * <td>21+</td> + * <td>21+</td> + * </tr> + * <tr> + * <td>TLS_PSK_WITH_AES_256_CBC_SHA</td> + * <td>21+</td> + * <td>21+</td> + * </tr> + * <tr> + * <td>TLS_PSK_WITH_RC4_128_SHA</td> + * <td>21+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_128_CBC_SHA</td> + * <td>9+</td> + * <td>9+</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_128_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_128_GCM_SHA256</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_256_CBC_SHA</td> + * <td>9+</td> + * <td>11+</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_256_CBC_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_AES_256_GCM_SHA384</td> + * <td>20+</td> + * <td>20+</td> + * </tr> + * <tr> + * <td>TLS_RSA_WITH_NULL_SHA256</td> + * <td>20+</td> + * <td></td> + * </tr> + * </tbody> + * </table> + * + * <p><em>NOTE</em>: PSK cipher suites are enabled by default only if the {@code SSLContext} through + * which the socket was created has been initialized with a {@code PSKKeyManager}. + * + * <p>API Levels 1 to 8 use OpenSSL names for cipher suites. The table below + * lists these OpenSSL names and their corresponding standard names used in API + * Levels 9 and newer. + * <table> + * <thead> + * <tr> + * <th>OpenSSL cipher suite</th> + * <th>Standard cipher suite</th> + * <th>Supported (API Levels)</th> + * <th>Enabled by default (API Levels)</th> + * </tr> + * </thead> + * + * <tbody> + * <tr> + * <td>AES128-SHA</td> + * <td>TLS_RSA_WITH_AES_128_CBC_SHA</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>AES256-SHA</td> + * <td>TLS_RSA_WITH_AES_256_CBC_SHA</td> + * <td>1+</td> + * <td>1-8, 11+</td> + * </tr> + * <tr> + * <td>DES-CBC-MD5</td> + * <td>SSL_CK_DES_64_CBC_WITH_MD5</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>DES-CBC-SHA</td> + * <td>SSL_RSA_WITH_DES_CBC_SHA</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>DES-CBC3-MD5</td> + * <td>SSL_CK_DES_192_EDE3_CBC_WITH_MD5</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>DES-CBC3-SHA</td> + * <td>SSL_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>DHE-DSS-AES128-SHA</td> + * <td>TLS_DHE_DSS_WITH_AES_128_CBC_SHA</td> + * <td>1+</td> + * <td>1+</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> + * </tr> + * <tr> + * <td>DHE-RSA-AES128-SHA</td> + * <td>TLS_DHE_RSA_WITH_AES_128_CBC_SHA</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * <tr> + * <td>DHE-RSA-AES256-SHA</td> + * <td>TLS_DHE_RSA_WITH_AES_256_CBC_SHA</td> + * <td>1+</td> + * <td>1-8, 11+</td> + * </tr> + * <tr> + * <td>EDH-DSS-DES-CBC-SHA</td> + * <td>SSL_DHE_DSS_WITH_DES_CBC_SHA</td> + * <td>1+</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-19</td> + * </tr> + * <tr> + * <td>EDH-RSA-DES-CBC-SHA</td> + * <td>SSL_DHE_RSA_WITH_DES_CBC_SHA</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>EDH-RSA-DES-CBC3-SHA</td> + * <td>SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>EXP-DES-CBC-SHA</td> + * <td>SSL_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>EXP-EDH-DSS-DES-CBC-SHA</td> + * <td>SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>EXP-EDH-RSA-DES-CBC-SHA</td> + * <td>SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>EXP-RC2-CBC-MD5</td> + * <td>SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>EXP-RC4-MD5</td> + * <td>SSL_RSA_EXPORT_WITH_RC4_40_MD5</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>RC2-CBC-MD5</td> + * <td>SSL_CK_RC2_128_CBC_WITH_MD5</td> + * <td>1-8</td> + * <td>1-8</td> + * </tr> + * <tr> + * <td>RC4-MD5</td> + * <td>SSL_RSA_WITH_RC4_128_MD5</td> + * <td>1+</td> + * <td>1-19</td> + * </tr> + * <tr> + * <td>RC4-SHA</td> + * <td>SSL_RSA_WITH_RC4_128_SHA</td> + * <td>1+</td> + * <td>1+</td> + * </tr> + * </tbody> + * </table> */ public abstract class SSLSocket extends Socket { @@ -188,13 +897,11 @@ public abstract class SSLSocket extends Socket { public abstract SSLSession getSession(); /** - * Registers the specified listener to receive notification on completion of a - * handshake on this connection. + * Registers the specified listener to receive notification on completion of + * a handshake on this connection. * - * @param listener - * the listener to register. - * @throws IllegalArgumentException - * if {@code listener} is {@code null}. + * @param listener the listener to register. + * @throws IllegalArgumentException if {@code listener} is {@code null}. */ public abstract void addHandshakeCompletedListener(HandshakeCompletedListener listener); diff --git a/luni/src/main/java/javax/net/ssl/SSLSocketFactory.java b/luni/src/main/java/javax/net/ssl/SSLSocketFactory.java index b07d0fd..b506fa6 100644 --- a/luni/src/main/java/javax/net/ssl/SSLSocketFactory.java +++ b/luni/src/main/java/javax/net/ssl/SSLSocketFactory.java @@ -22,6 +22,7 @@ import java.net.Socket; import java.security.NoSuchAlgorithmException; import java.security.Security; import javax.net.SocketFactory; +import org.apache.harmony.security.fortress.Services; /** * The abstract factory implementation to create {@code SSLSocket}s. @@ -32,7 +33,7 @@ public abstract class SSLSocketFactory extends SocketFactory { // The default SSL socket factory private static SocketFactory defaultSocketFactory; - private static String defaultName; + private static int lastCacheVersion = -1; /** * Returns the default {@code SSLSocketFactory} instance. The default is @@ -41,23 +42,39 @@ public abstract class SSLSocketFactory extends SocketFactory { * @return the default ssl socket factory instance. */ public static synchronized SocketFactory getDefault() { - if (defaultSocketFactory != null) { + int newCacheVersion = Services.getCacheVersion(); + if (defaultSocketFactory != null && lastCacheVersion == newCacheVersion) { return defaultSocketFactory; } - if (defaultName == null) { - defaultName = Security.getProperty("ssl.SocketFactory.provider"); - if (defaultName != null) { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl == null) { - cl = ClassLoader.getSystemClassLoader(); - } - try { - final Class<?> sfc = Class.forName(defaultName, true, cl); - defaultSocketFactory = (SocketFactory) sfc.newInstance(); - } catch (Exception e) { - System.logE("Problem creating " + defaultName, e); + lastCacheVersion = newCacheVersion; + + String newName = Security.getProperty("ssl.SocketFactory.provider"); + if (newName != null) { + /* The cache could have been invalidated, but the provider name didn't change. This + * will be the most common state, so check for it early without resetting the default + * SocketFactory. + */ + if (defaultSocketFactory != null) { + if (newName.equals(defaultSocketFactory.getClass().getName())) { + return defaultSocketFactory; + } else { + defaultSocketFactory = null; } } + + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = ClassLoader.getSystemClassLoader(); + } + try { + final Class<?> sfc = Class.forName(newName, true, cl); + defaultSocketFactory = (SocketFactory) sfc.newInstance(); + } catch (Exception e) { + System.logW("Could not create " + newName + " with ClassLoader " + + cl.toString() + ": " + e.getMessage()); + } + } else { + defaultSocketFactory = null; } if (defaultSocketFactory == null) { @@ -71,10 +88,12 @@ public abstract class SSLSocketFactory extends SocketFactory { defaultSocketFactory = context.getSocketFactory(); } } + if (defaultSocketFactory == null) { // Use internal implementation defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed"); } + return defaultSocketFactory; } diff --git a/luni/src/main/java/javax/security/cert/Certificate.java b/luni/src/main/java/javax/security/cert/Certificate.java index b3e31f6..08ce36b 100644 --- a/luni/src/main/java/javax/security/cert/Certificate.java +++ b/luni/src/main/java/javax/security/cert/Certificate.java @@ -126,15 +126,15 @@ public abstract class Certificate { * public key for which verification should be performed. * @param sigProvider * the name of the signature provider. - * @exception CertificateException + * @throws CertificateException * if encoding errors are detected - * @exception NoSuchAlgorithmException + * @throws NoSuchAlgorithmException * if an unsupported algorithm is detected - * @exception InvalidKeyException + * @throws InvalidKeyException * if an invalid key is detected - * @exception NoSuchProviderException + * @throws NoSuchProviderException * if the specified provider does not exists. - * @exception SignatureException + * @throws SignatureException * if signature errors are detected */ public abstract void verify(PublicKey key, String sigProvider) @@ -157,4 +157,3 @@ public abstract class Certificate { */ public abstract PublicKey getPublicKey(); } - diff --git a/luni/src/main/java/javax/xml/transform/overview.html b/luni/src/main/java/javax/xml/transform/overview.html index 918db9b..fe3372b 100644 --- a/luni/src/main/java/javax/xml/transform/overview.html +++ b/luni/src/main/java/javax/xml/transform/overview.html @@ -177,7 +177,7 @@ <H3>TRaX Patterns</H3> <ul> <p> -<b><a name="pattern-Processor">Processor</a></b> +<b><a name="pattern-Processor"></a>Processor</b> <br> <br> <i>Intent: </i>Generic concept for the @@ -191,7 +191,7 @@ operations. Different Processors can be used concurrently by different threads.</p> <p> -<b><a name="pattern-TransformerFactory">TransformerFactory</a></b> +<b><a name="pattern-TransformerFactory"></a>TransformerFactory</b> <br> <br> <i>Intent: </i>Serve as a vendor-neutral Processor interface for @@ -205,7 +205,7 @@ TransformerFactory may not perform multiple concurrent operations.</p> <p> -<b><a name="pattern-Templates">Templates</a></b> +<b><a name="pattern-Templates"></a>Templates</b> <br> <br> <i>Intent: </i>The @@ -215,7 +215,7 @@ <i>Thread safety: </i>Thread-safe for concurrent usage over multiple threads once construction is complete.</p> <p> -<b><a name="pattern-Transformer">Transformer</a></b> +<b><a name="pattern-Transformer"></a>Transformer</b> <br> <br> <i>Intent: </i>Act as a per-thread @@ -228,7 +228,7 @@ <i>Notes: </i>The Transformer is bound to the Templates object that created it.</p> <p> -<b><a name="pattern-Source">Source</a></b> +<b><a name="pattern-Source"></a>Source</b> <br> <br> <i>Intent: </i>Serve as a @@ -239,7 +239,7 @@ threads for read-only operations; must be synchronized for edit operations.</p> <p> -<b><a name="pattern-Result">Result</a></b> +<b><a name="pattern-Result"></a>Result</b> <br> <br> <i>Potential alternate name: </i>ResultTarget<br> diff --git a/luni/src/main/java/libcore/icu/CollationElementIteratorICU.java b/luni/src/main/java/libcore/icu/CollationElementIteratorICU.java index 5779d17..d9f105d 100644 --- a/luni/src/main/java/libcore/icu/CollationElementIteratorICU.java +++ b/luni/src/main/java/libcore/icu/CollationElementIteratorICU.java @@ -37,15 +37,6 @@ import java.text.CharacterIterator; * @stable ICU 2.4 */ public final class CollationElementIteratorICU { - // public data member ------------------------------------------- - - /** - * @stable ICU 2.4 - */ - public static final int NULLORDER = 0xFFFFFFFF; - - // public methods ----------------------------------------------- - /** * Reset the collation elements to their initial state. * This will move the 'cursor' to the beginning of the text. diff --git a/luni/src/main/java/libcore/icu/DateIntervalFormat.java b/luni/src/main/java/libcore/icu/DateIntervalFormat.java index ab9085f..3855654 100644 --- a/luni/src/main/java/libcore/icu/DateIntervalFormat.java +++ b/luni/src/main/java/libcore/icu/DateIntervalFormat.java @@ -92,7 +92,7 @@ public final class DateIntervalFormat { // This is not the behavior of icu4c's DateIntervalFormat, but it's the historical behavior // of Android's DateUtils.formatDateRange. if (startMs != endMs && endsAtMidnight && - ((flags & FORMAT_SHOW_TIME) == 0 || julianDay(startCalendar) == julianDay(endCalendar))) { + ((flags & FORMAT_SHOW_TIME) == 0 || dayDistance(startCalendar, endCalendar) <= 1)) { endCalendar.roll(Calendar.DAY_OF_MONTH, false); endMs -= DAY_IN_MS; } @@ -224,8 +224,12 @@ public final class DateIntervalFormat { 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.get(Calendar.MILLISECOND) + c.get(Calendar.ZONE_OFFSET); + long utcMs = c.getTimeInMillis() + c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET); return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY; } diff --git a/luni/src/main/java/libcore/icu/ICU.java b/luni/src/main/java/libcore/icu/ICU.java index 76d9c54..0ef3f93 100644 --- a/luni/src/main/java/libcore/icu/ICU.java +++ b/luni/src/main/java/libcore/icu/ICU.java @@ -16,8 +16,13 @@ package libcore.icu; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Locale; +import java.util.Map; +import java.util.Set; import libcore.util.BasicLruCache; /** @@ -53,30 +58,173 @@ public final class ICU { return isoCountries.clone(); } + private static final int IDX_LANGUAGE = 0; + private static final int IDX_SCRIPT = 1; + private static final int IDX_REGION = 2; + private static final int IDX_VARIANT = 3; + + /* + * Parse the {Language, Script, Region, Variant*} section of the ICU locale + * ID. This is the bit that appears before the keyword separate "@". The general + * structure is a series of ASCII alphanumeric strings (subtags) + * separated by underscores. + * + * Each subtag is interpreted according to its position in the list of subtags + * AND its length (groan...). The various cases are explained in comments + * below. + */ + private static void parseLangScriptRegionAndVariants(String string, + String[] outputArray) { + final int first = string.indexOf('_'); + final int second = string.indexOf('_', first + 1); + final int third = string.indexOf('_', second + 1); + + if (first == -1) { + outputArray[IDX_LANGUAGE] = string; + } else if (second == -1) { + // Language and country ("ja_JP") OR + // Language and script ("en_Latn") OR + // Language and variant ("en_POSIX"). + + outputArray[IDX_LANGUAGE] = string.substring(0, first); + final String secondString = string.substring(first + 1); + + if (secondString.length() == 4) { + // 4 Letter ISO script code. + outputArray[IDX_SCRIPT] = secondString; + } else if (secondString.length() == 2 || secondString.length() == 3) { + // 2 or 3 Letter region code. + outputArray[IDX_REGION] = secondString; + } else { + // If we're here, the length of the second half is either 1 or greater + // than 5. Assume that ICU won't hand us malformed tags, and therefore + // assume the rest of the string is a series of variant tags. + outputArray[IDX_VARIANT] = secondString; + } + } else if (third == -1) { + // Language and country and variant ("ja_JP_TRADITIONAL") OR + // Language and script and variant ("en_Latn_POSIX") OR + // Language and script and region ("en_Latn_US"). OR + // Language and variant with multiple subtags ("en_POSIX_XISOP") + + outputArray[IDX_LANGUAGE] = string.substring(0, first); + final String secondString = string.substring(first + 1, second); + final String thirdString = string.substring(second + 1); + + if (secondString.length() == 4) { + // The second subtag is a script. + outputArray[IDX_SCRIPT] = secondString; + + // The third subtag can be either a region or a variant, depending + // on its length. + if (thirdString.length() == 2 || thirdString.length() == 3 || + thirdString.isEmpty()) { + outputArray[IDX_REGION] = thirdString; + } else { + outputArray[IDX_VARIANT] = thirdString; + } + } else if (secondString.isEmpty() || + secondString.length() == 2 || secondString.length() == 3) { + // The second string is a region, and the third a variant. + outputArray[IDX_REGION] = secondString; + outputArray[IDX_VARIANT] = thirdString; + } else { + // Variant with multiple subtags. + outputArray[IDX_VARIANT] = string.substring(first + 1); + } + } else { + // Language, script, region and variant with 1 or more subtags + // ("en_Latn_US_POSIX") OR + // Language, region and variant with 2 or more subtags + // (en_US_POSIX_VARIANT). + outputArray[IDX_LANGUAGE] = string.substring(0, first); + final String secondString = string.substring(first + 1, second); + if (secondString.length() == 4) { + outputArray[IDX_SCRIPT] = secondString; + outputArray[IDX_REGION] = string.substring(second + 1, third); + outputArray[IDX_VARIANT] = string.substring(third + 1); + } else { + outputArray[IDX_REGION] = secondString; + outputArray[IDX_VARIANT] = string.substring(second + 1); + } + } + } + /** * Returns the appropriate {@code Locale} given a {@code String} of the form returned * by {@code toString}. This is very lenient, and doesn't care what's between the underscores: * this method can parse strings that {@code Locale.toString} won't produce. * Used to remove duplication. */ - public static Locale localeFromString(String localeName) { - int first = localeName.indexOf('_'); - int second = localeName.indexOf('_', first + 1); - if (first == -1) { - // Language only ("ja"). - return new Locale(localeName); - } else if (second == -1) { - // Language and country ("ja_JP"). - String language = localeName.substring(0, first); - String country = localeName.substring(first + 1); - return new Locale(language, country); + public static Locale localeFromIcuLocaleId(String localeId) { + // @ == ULOC_KEYWORD_SEPARATOR_UNICODE (uloc.h). + final int extensionsIndex = localeId.indexOf('@'); + + Map<Character, String> extensionsMap = Collections.EMPTY_MAP; + Map<String, String> unicodeKeywordsMap = Collections.EMPTY_MAP; + Set<String> unicodeAttributeSet = Collections.EMPTY_SET; + + if (extensionsIndex != -1) { + extensionsMap = new HashMap<Character, String>(); + unicodeKeywordsMap = new HashMap<String, String>(); + unicodeAttributeSet = new HashSet<String>(); + + // ICU sends us a semi-colon (ULOC_KEYWORD_ITEM_SEPARATOR) delimited string + // containing all "keywords" it could parse. An ICU keyword is a key-value pair + // separated by an "=" (ULOC_KEYWORD_ASSIGN). + // + // Each keyword item can be one of three things : + // - A unicode extension attribute list: In this case the item key is "attribute" + // and the value is a hyphen separated list of unicode attributes. + // - A unicode extension keyword: In this case, the item key will be larger than + // 1 char in length, and the value will be the unicode extension value. + // - A BCP-47 extension subtag: In this case, the item key will be exactly one + // char in length, and the value will be a sequence of unparsed subtags that + // represent the extension. + // + // Note that this implies that unicode extension keywords are "promoted" to + // to the same namespace as the top level extension subtags and their values. + // There can't be any collisions in practice because the BCP-47 spec imposes + // restrictions on their lengths. + final String extensionsString = localeId.substring(extensionsIndex + 1); + final String[] extensions = extensionsString.split(";"); + for (String extension : extensions) { + // This is the special key for the unicode attributes + if (extension.startsWith("attribute=")) { + String unicodeAttributeValues = extension.substring("attribute=".length()); + for (String unicodeAttribute : unicodeAttributeValues.split("-")) { + unicodeAttributeSet.add(unicodeAttribute); + } + } else { + final int separatorIndex = extension.indexOf('='); + + if (separatorIndex == 1) { + // This is a BCP-47 extension subtag. + final String value = extension.substring(2); + final char extensionId = extension.charAt(0); + + extensionsMap.put(extensionId, value); + } else { + // This is a unicode extension keyword. + unicodeKeywordsMap.put(extension.substring(0, separatorIndex), + extension.substring(separatorIndex + 1)); + } + } + } + } + + final String[] outputArray = new String[] { "", "", "", "" }; + if (extensionsIndex == -1) { + parseLangScriptRegionAndVariants(localeId, outputArray); } else { - // Language and country and variant ("ja_JP_TRADITIONAL"). - String language = localeName.substring(0, first); - String country = localeName.substring(first + 1, second); - String variant = localeName.substring(second + 1); - return new Locale(language, country, variant); + parseLangScriptRegionAndVariants(localeId.substring(0, extensionsIndex), + outputArray); } + + return new Locale(outputArray[IDX_LANGUAGE], outputArray[IDX_REGION], + outputArray[IDX_VARIANT], outputArray[IDX_SCRIPT], + unicodeAttributeSet, unicodeKeywordsMap, extensionsMap, + true /* has validated fields */); } public static Locale[] localesFromStrings(String[] localeNames) { @@ -85,7 +233,7 @@ public final class ICU { // both so that we never need to convert back when talking to it. LinkedHashSet<Locale> set = new LinkedHashSet<Locale>(); for (String localeName : localeNames) { - set.add(localeFromString(localeName)); + set.add(localeFromIcuLocaleId(localeName)); } return set.toArray(new Locale[set.size()]); } @@ -125,19 +273,20 @@ public final class ICU { return localesFromStrings(getAvailableNumberFormatLocalesNative()); } - public static String getBestDateTimePattern(String skeleton, String localeName) { - String key = skeleton + "\t" + localeName; + public static String getBestDateTimePattern(String skeleton, Locale locale) { + String languageTag = locale.toLanguageTag(); + String key = skeleton + "\t" + languageTag; synchronized (CACHED_PATTERNS) { String pattern = CACHED_PATTERNS.get(key); if (pattern == null) { - pattern = getBestDateTimePatternNative(skeleton, localeName); + pattern = getBestDateTimePatternNative(skeleton, languageTag); CACHED_PATTERNS.put(key, pattern); } return pattern; } } - private static native String getBestDateTimePatternNative(String skeleton, String localeName); + private static native String getBestDateTimePatternNative(String skeleton, String languageTag); public static char[] getDateFormatOrder(String pattern) { char[] result = new char[3]; @@ -197,8 +346,17 @@ public final class ICU { // --- Case mapping. - public static native String toLowerCase(String s, String localeName); - public static native String toUpperCase(String s, String localeName); + public static String toLowerCase(String s, Locale locale) { + return toLowerCase(s, locale.toLanguageTag()); + } + + private static native String toLowerCase(String s, String languageTag); + + public static String toUpperCase(String s, Locale locale) { + return toUpperCase(s, locale.toLanguageTag()); + } + + private static native String toUpperCase(String s, String languageTag); // --- Errors. @@ -224,22 +382,79 @@ public final class ICU { public static native String[] getAvailableCurrencyCodes(); public static native String getCurrencyCode(String countryCode); - public static native String getCurrencyDisplayName(String locale, String currencyCode); + + public static String getCurrencyDisplayName(Locale locale, String currencyCode) { + return getCurrencyDisplayName(locale.toLanguageTag(), currencyCode); + } + + private static native String getCurrencyDisplayName(String languageTag, String currencyCode); + public static native int getCurrencyFractionDigits(String currencyCode); - public static native String getCurrencySymbol(String locale, String currencyCode); + public static native int getCurrencyNumericCode(String currencyCode); + + public static String getCurrencySymbol(Locale locale, String currencyCode) { + return getCurrencySymbol(locale.toLanguageTag(), currencyCode); + } + + private static native String getCurrencySymbol(String languageTag, String currencyCode); + + public static String getDisplayCountry(Locale targetLocale, Locale locale) { + return getDisplayCountryNative(targetLocale.toLanguageTag(), locale.toLanguageTag()); + } - public static native String getDisplayCountryNative(String countryCode, String locale); - public static native String getDisplayLanguageNative(String languageCode, String locale); - public static native String getDisplayVariantNative(String variantCode, String locale); + private static native String getDisplayCountryNative(String targetLanguageTag, String languageTag); + + public static String getDisplayLanguage(Locale targetLocale, Locale locale) { + return getDisplayLanguageNative(targetLocale.toLanguageTag(), locale.toLanguageTag()); + } + + private static native String getDisplayLanguageNative(String targetLanguageTag, String languageTag); + + public static String getDisplayVariant(Locale targetLocale, Locale locale) { + return getDisplayVariantNative(targetLocale.toLanguageTag(), locale.toLanguageTag()); + } + + private static native String getDisplayVariantNative(String targetLanguageTag, String languageTag); + + public static String getDisplayScript(Locale targetLocale, Locale locale) { + return getDisplayScriptNative(targetLocale.toLanguageTag(), locale.toLanguageTag()); + } - public static native String getISO3CountryNative(String locale); - public static native String getISO3LanguageNative(String locale); + private static native String getDisplayScriptNative(String targetLanguageTag, String languageTag); + public static native String getISO3Country(String languageTag); + + public static native String getISO3Language(String languageTag); + + public static Locale addLikelySubtags(Locale locale) { + return Locale.forLanguageTag(addLikelySubtags(locale.toLanguageTag()).replace('_', '-')); + } + + /** + * @deprecated use {@link #addLikelySubtags(java.util.Locale)} instead. + */ + @Deprecated public static native String addLikelySubtags(String locale); + + /** + * @deprecated use {@link java.util.Locale#getScript()} instead. This has been kept + * around only for the support library. + */ + @Deprecated public static native String getScript(String locale); private static native String[] getISOLanguagesNative(); private static native String[] getISOCountriesNative(); - static native boolean initLocaleDataNative(String locale, LocaleData result); + static native boolean initLocaleDataNative(String languageTag, LocaleData result); + + /** + * Takes a BCP-47 language tag (Locale.toLanguageTag()). e.g. en-US, not en_US + */ + public static native void setDefaultLocale(String languageTag); + + /** + * Returns a locale name, not a BCP-47 language tag. e.g. en_US not en-US. + */ + public static native String getDefaultLocale(); } diff --git a/luni/src/main/java/libcore/icu/LocaleData.java b/luni/src/main/java/libcore/icu/LocaleData.java index f00c30f..9e07244 100644 --- a/luni/src/main/java/libcore/icu/LocaleData.java +++ b/luni/src/main/java/libcore/icu/LocaleData.java @@ -79,6 +79,10 @@ public final class LocaleData { public String mediumDateFormat; public String shortDateFormat; + // Used by TimePicker. Not currently used by UTS#35. + public String narrowAm; // "a". + public String narrowPm; // "p". + // shortDateFormat, but guaranteed to have 4-digit years. // Used by android.text.format.DateFormat.getDateFormatStringForSetting. public String shortDateFormat4; @@ -95,7 +99,7 @@ public final class LocaleData { public char percent; public char perMill; public char monetarySeparator; - public char minusSign; + public String minusSign; public String exponentSeparator; public String infinity; public String NaN; @@ -112,27 +116,40 @@ public final class LocaleData { private LocaleData() { } + public static Locale mapInvalidAndNullLocales(Locale locale) { + if (locale == null) { + return Locale.getDefault(); + } + + if ("und".equals(locale.toLanguageTag())) { + return Locale.ROOT; + } + + return locale; + } + /** * Returns a shared LocaleData for the given locale. */ public static LocaleData get(Locale locale) { if (locale == null) { - locale = Locale.getDefault(); + throw new NullPointerException("locale == null"); } - String localeName = locale.toString(); + + final String languageTag = locale.toLanguageTag(); synchronized (localeDataCache) { - LocaleData localeData = localeDataCache.get(localeName); + LocaleData localeData = localeDataCache.get(languageTag); if (localeData != null) { return localeData; } } LocaleData newLocaleData = initLocaleData(locale); synchronized (localeDataCache) { - LocaleData localeData = localeDataCache.get(localeName); + LocaleData localeData = localeDataCache.get(languageTag); if (localeData != null) { return localeData; } - localeDataCache.put(localeName, newLocaleData); + localeDataCache.put(languageTag, newLocaleData); return newLocaleData; } } @@ -171,13 +188,13 @@ public final class LocaleData { private static LocaleData initLocaleData(Locale locale) { LocaleData localeData = new LocaleData(); - if (!ICU.initLocaleDataNative(locale.toString(), localeData)) { + if (!ICU.initLocaleDataNative(locale.toLanguageTag(), localeData)) { throw new AssertionError("couldn't initialize LocaleData for locale " + locale); } // Get the "h:mm a" and "HH:mm" 12- and 24-hour time format strings. - localeData.timeFormat12 = ICU.getBestDateTimePattern("hm", locale.toString()); - localeData.timeFormat24 = ICU.getBestDateTimePattern("Hm", locale.toString()); + localeData.timeFormat12 = ICU.getBestDateTimePattern("hm", locale); + localeData.timeFormat24 = ICU.getBestDateTimePattern("Hm", locale); // 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 index 7168d96..992aac2 100644 --- a/luni/src/main/java/libcore/icu/NativeBreakIterator.java +++ b/luni/src/main/java/libcore/icu/NativeBreakIterator.java @@ -138,23 +138,23 @@ public final class NativeBreakIterator implements Cloneable { } public int preceding(int offset) { - return precedingImpl(this.address, this.string, offset); + return precedingImpl(this.address, this.string, offset); } - public static NativeBreakIterator getCharacterInstance(Locale where) { - return new NativeBreakIterator(getCharacterInstanceImpl(where.toString()), BI_CHAR_INSTANCE); + public static NativeBreakIterator getCharacterInstance(Locale locale) { + return new NativeBreakIterator(getCharacterInstanceImpl(locale.toLanguageTag()), BI_CHAR_INSTANCE); } - public static NativeBreakIterator getLineInstance(Locale where) { - return new NativeBreakIterator(getLineInstanceImpl(where.toString()), BI_LINE_INSTANCE); + public static NativeBreakIterator getLineInstance(Locale locale) { + return new NativeBreakIterator(getLineInstanceImpl(locale.toLanguageTag()), BI_LINE_INSTANCE); } - public static NativeBreakIterator getSentenceInstance(Locale where) { - return new NativeBreakIterator(getSentenceInstanceImpl(where.toString()), BI_SENT_INSTANCE); + public static NativeBreakIterator getSentenceInstance(Locale locale) { + return new NativeBreakIterator(getSentenceInstanceImpl(locale.toLanguageTag()), BI_SENT_INSTANCE); } - public static NativeBreakIterator getWordInstance(Locale where) { - return new NativeBreakIterator(getWordInstanceImpl(where.toString()), BI_WORD_INSTANCE); + public static NativeBreakIterator getWordInstance(Locale locale) { + return new NativeBreakIterator(getWordInstanceImpl(locale.toLanguageTag()), BI_WORD_INSTANCE); } private static native long getCharacterInstanceImpl(String locale); diff --git a/luni/src/main/java/libcore/icu/NativeCollation.java b/luni/src/main/java/libcore/icu/NativeCollation.java index 0373fef..b4b4f46 100644 --- a/luni/src/main/java/libcore/icu/NativeCollation.java +++ b/luni/src/main/java/libcore/icu/NativeCollation.java @@ -10,6 +10,8 @@ package libcore.icu; +import java.util.Locale; + /** * Package static class for declaring all native methods for collation use. * @author syn wee quek @@ -23,10 +25,13 @@ public final class NativeCollation { public static native void closeCollator(long address); public static native int compare(long address, String source, String target); public static native int getAttribute(long address, int type); - public static native int getCollationElementIterator(long address, String source); + public static native long getCollationElementIterator(long address, String source); public static native String getRules(long address); public static native byte[] getSortKey(long address, String source); - public static native long openCollator(String locale); + public static long openCollator(Locale locale) { + return openCollator(locale.toLanguageTag()); + } + private static native long openCollator(String languageTag); public static native long openCollatorFromRules(String rules, int normalizationMode, int collationStrength); public static native long safeClone(long address); public static native void setAttribute(long address, int type, int value); diff --git a/luni/src/main/java/libcore/icu/NativeDecimalFormat.java b/luni/src/main/java/libcore/icu/NativeDecimalFormat.java index 0e9ffc4..fd179c1 100644 --- a/luni/src/main/java/libcore/icu/NativeDecimalFormat.java +++ b/luni/src/main/java/libcore/icu/NativeDecimalFormat.java @@ -27,7 +27,6 @@ import java.text.Format; import java.text.NumberFormat; import java.text.ParsePosition; import java.util.Currency; -import java.util.NoSuchElementException; public final class NativeDecimalFormat implements Cloneable { /** @@ -92,6 +91,47 @@ public final class NativeDecimalFormat implements Cloneable { private static final int UNUM_PUBLIC_RULESETS = 7; /** + * A table for translating between NumberFormat.Field instances + * and icu4c UNUM_x_FIELD constants. + */ + private static final Format.Field[] ICU4C_FIELD_IDS = { + // The old java field values were 0 for integer and 1 for fraction. + // The new java field attributes are all objects. ICU assigns the values + // starting from 0 in the following order; note that integer and + // fraction positions match the old field values. + NumberFormat.Field.INTEGER, // 0 UNUM_INTEGER_FIELD + NumberFormat.Field.FRACTION, // 1 UNUM_FRACTION_FIELD + NumberFormat.Field.DECIMAL_SEPARATOR, // 2 UNUM_DECIMAL_SEPARATOR_FIELD + NumberFormat.Field.EXPONENT_SYMBOL, // 3 UNUM_EXPONENT_SYMBOL_FIELD + NumberFormat.Field.EXPONENT_SIGN, // 4 UNUM_EXPONENT_SIGN_FIELD + NumberFormat.Field.EXPONENT, // 5 UNUM_EXPONENT_FIELD + NumberFormat.Field.GROUPING_SEPARATOR, // 6 UNUM_GROUPING_SEPARATOR_FIELD + NumberFormat.Field.CURRENCY, // 7 UNUM_CURRENCY_FIELD + NumberFormat.Field.PERCENT, // 8 UNUM_PERCENT_FIELD + NumberFormat.Field.PERMILLE, // 9 UNUM_PERMILL_FIELD + NumberFormat.Field.SIGN, // 10 UNUM_SIGN_FIELD + }; + + private static int translateFieldId(FieldPosition fp) { + int id = fp.getField(); + if (id < -1 || id > 1) { + id = -1; + } + if (id == -1) { + Format.Field attr = fp.getFieldAttribute(); + if (attr != null) { + for (int i = 0; i < ICU4C_FIELD_IDS.length; ++i) { + if (ICU4C_FIELD_IDS[i].equals(attr)) { + id = i; + break; + } + } + } + } + return id; + } + + /** * The address of the ICU DecimalFormat* on the native heap. */ private long address; @@ -111,19 +151,12 @@ public final class NativeDecimalFormat implements Cloneable { private transient boolean parseBigDecimal; - /** - * Cache the BigDecimal form of the multiplier. This is null until we've - * formatted a BigDecimal (with a multiplier that is not 1), or the user has - * explicitly called {@link #setMultiplier(int)} with any multiplier. - */ - private BigDecimal multiplierBigDecimal = null; - public NativeDecimalFormat(String pattern, DecimalFormatSymbols dfs) { try { this.address = open(pattern, dfs.getCurrencySymbol(), dfs.getDecimalSeparator(), dfs.getDigit(), dfs.getExponentSeparator(), dfs.getGroupingSeparator(), dfs.getInfinity(), - dfs.getInternationalCurrencySymbol(), dfs.getMinusSign(), + dfs.getInternationalCurrencySymbol(), dfs.getMinusSignString(), dfs.getMonetaryDecimalSeparator(), dfs.getNaN(), dfs.getPatternSeparator(), dfs.getPercent(), dfs.getPerMill(), dfs.getZeroDigit()); this.lastPattern = pattern; @@ -211,13 +244,30 @@ public final class NativeDecimalFormat implements Cloneable { obj.isGroupingUsed() == this.isGroupingUsed(); } + public String toString() { + return getClass().getName() + "[\"" + toPattern() + "\"" + + ",isDecimalSeparatorAlwaysShown=" + isDecimalSeparatorAlwaysShown() + + ",groupingSize=" + getGroupingSize() + + ",multiplier=" + getMultiplier() + + ",negativePrefix=" + getNegativePrefix() + + ",negativeSuffix=" + getNegativeSuffix() + + ",positivePrefix=" + getPositivePrefix() + + ",positiveSuffix=" + getPositiveSuffix() + + ",maxIntegerDigits=" + getMaximumIntegerDigits() + + ",maxFractionDigits=" + getMaximumFractionDigits() + + ",minIntegerDigits=" + getMinimumIntegerDigits() + + ",minFractionDigits=" + getMinimumFractionDigits() + + ",grouping=" + isGroupingUsed() + + "]"; + } + /** * Copies the DecimalFormatSymbols settings into our native peer in bulk. */ public void setDecimalFormatSymbols(final DecimalFormatSymbols dfs) { setDecimalFormatSymbols(this.address, dfs.getCurrencySymbol(), dfs.getDecimalSeparator(), dfs.getDigit(), dfs.getExponentSeparator(), dfs.getGroupingSeparator(), - dfs.getInfinity(), dfs.getInternationalCurrencySymbol(), dfs.getMinusSign(), + dfs.getInfinity(), dfs.getInternationalCurrencySymbol(), dfs.getMinusSignString(), dfs.getMonetaryDecimalSeparator(), dfs.getNaN(), dfs.getPatternSeparator(), dfs.getPercent(), dfs.getPerMill(), dfs.getZeroDigit()); } @@ -233,8 +283,8 @@ public final class NativeDecimalFormat implements Cloneable { public char[] formatBigDecimal(BigDecimal value, FieldPosition field) { FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field); char[] result = formatDigitList(this.address, value.toString(), fpi); - if (fpi != null) { - FieldPositionIterator.setFieldPosition(fpi, field); + if (fpi != null && field != null) { + updateFieldPosition(field, fpi); } return result; } @@ -242,8 +292,8 @@ public final class NativeDecimalFormat implements Cloneable { public char[] formatBigInteger(BigInteger value, FieldPosition field) { FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field); char[] result = formatDigitList(this.address, value.toString(10), fpi); - if (fpi != null) { - FieldPositionIterator.setFieldPosition(fpi, field); + if (fpi != null && field != null) { + updateFieldPosition(field, fpi); } return result; } @@ -251,8 +301,8 @@ public final class NativeDecimalFormat implements Cloneable { public char[] formatLong(long value, FieldPosition field) { FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field); char[] result = formatLong(this.address, value, fpi); - if (fpi != null) { - FieldPositionIterator.setFieldPosition(fpi, field); + if (fpi != null && field != null) { + updateFieldPosition(field, fpi); } return result; } @@ -260,12 +310,25 @@ public final class NativeDecimalFormat implements Cloneable { public char[] formatDouble(double value, FieldPosition field) { FieldPositionIterator fpi = FieldPositionIterator.forFieldPosition(field); char[] result = formatDouble(this.address, value, fpi); - if (fpi != null) { - FieldPositionIterator.setFieldPosition(fpi, field); + if (fpi != null && field != null) { + updateFieldPosition(field, fpi); } return result; } + private static void updateFieldPosition(FieldPosition fp, FieldPositionIterator fpi) { + int field = translateFieldId(fp); + if (field != -1) { + while (fpi.next()) { + if (fpi.fieldId() == field) { + fp.setBeginIndex(fpi.start()); + fp.setEndIndex(fpi.limit()); + return; + } + } + } + } + public void applyLocalizedPattern(String pattern) { applyPattern(this.address, true, pattern); lastPattern = null; @@ -352,6 +415,10 @@ public final class NativeDecimalFormat implements Cloneable { } public int getGroupingSize() { + // Work around http://bugs.icu-project.org/trac/ticket/10864 in icu4c 53. + if (!isGroupingUsed()) { + return 0; + } return getAttribute(this.address, UNUM_GROUPING_SIZE); } @@ -408,9 +475,9 @@ public final class NativeDecimalFormat implements Cloneable { setAttribute(this.address, UNUM_DECIMAL_ALWAYS_SHOWN, i); } - public void setCurrency(Currency currency) { - setSymbol(this.address, UNUM_CURRENCY_SYMBOL, currency.getSymbol()); - setSymbol(this.address, UNUM_INTL_CURRENCY_SYMBOL, currency.getCurrencyCode()); + public void setCurrency(String currencySymbol, String currencyCode) { + setSymbol(this.address, UNUM_CURRENCY_SYMBOL, currencySymbol); + setSymbol(this.address, UNUM_INTL_CURRENCY_SYMBOL, currencyCode); } public void setGroupingSize(int value) { @@ -440,8 +507,6 @@ public final class NativeDecimalFormat implements Cloneable { public void setMultiplier(int value) { setAttribute(this.address, UNUM_MULTIPLIER, value); - // Update the cached BigDecimal for multiplier. - multiplierBigDecimal = BigDecimal.valueOf(value); } public void setNegativePrefix(String value) { @@ -501,6 +566,7 @@ public final class NativeDecimalFormat implements Cloneable { case HALF_EVEN: nativeRoundingMode = 4; break; case HALF_DOWN: nativeRoundingMode = 5; break; case HALF_UP: nativeRoundingMode = 6; break; + case UNNECESSARY: nativeRoundingMode = 7; break; default: throw new AssertionError(); } setRoundingMode(address, nativeRoundingMode, roundingIncrement); @@ -515,104 +581,33 @@ public final class NativeDecimalFormat implements Cloneable { } public static FieldPositionIterator forFieldPosition(FieldPosition fp) { - if (fp != null && fp.getField() != -1) { - return new FieldPositionIterator(); - } - return null; - } - - private static int getNativeFieldPositionId(FieldPosition fp) { - // NOTE: -1, 0, and 1 were the only valid original java field values - // for NumberFormat. They take precedence. This assumes any other - // value is a mistake and the actual value is in the attribute. - // Clients can construct FieldPosition combining any attribute with any field - // value, which is just wrong, but there you go. - - int id = fp.getField(); - if (id < -1 || id > 1) { - id = -1; - } - if (id == -1) { - Format.Field attr = fp.getFieldAttribute(); - if (attr != null) { - for (int i = 0; i < fields.length; ++i) { - if (fields[i].equals(attr)) { - id = i; - break; - } - } - } - } - return id; - } - - private static void setFieldPosition(FieldPositionIterator fpi, FieldPosition fp) { - if (fpi != null && fp != null) { - int field = getNativeFieldPositionId(fp); - if (field != -1) { - while (fpi.next()) { - if (fpi.fieldId() == field) { - fp.setBeginIndex(fpi.start()); - fp.setEndIndex(fpi.limit()); - break; - } - } - } - } + return (fp != null) ? new FieldPositionIterator() : null; } public boolean next() { - // if pos == data.length, we've already returned false once - if (data == null || pos == data.length) { - throw new NoSuchElementException(); + if (data == null) { + return false; } pos += 3; return pos < data.length; } - private void checkValid() { - if (data == null || pos < 0 || pos == data.length) { - throw new NoSuchElementException(); - } - } - public int fieldId() { return data[pos]; } public Format.Field field() { - checkValid(); - return fields[data[pos]]; + return ICU4C_FIELD_IDS[data[pos]]; } public int start() { - checkValid(); return data[pos + 1]; } public int limit() { - checkValid(); return data[pos + 2]; } - private static Format.Field fields[] = { - // The old java field values were 0 for integer and 1 for fraction. - // The new java field attributes are all objects. ICU assigns the values - // starting from 0 in the following order; note that integer and - // fraction positions match the old field values. - NumberFormat.Field.INTEGER, - NumberFormat.Field.FRACTION, - NumberFormat.Field.DECIMAL_SEPARATOR, - NumberFormat.Field.EXPONENT_SYMBOL, - NumberFormat.Field.EXPONENT_SIGN, - NumberFormat.Field.EXPONENT, - NumberFormat.Field.GROUPING_SEPARATOR, - NumberFormat.Field.CURRENCY, - NumberFormat.Field.PERCENT, - NumberFormat.Field.PERMILLE, - NumberFormat.Field.SIGN, - }; - // called by native private void setData(int[] data) { this.data = data; @@ -630,13 +625,13 @@ public final class NativeDecimalFormat implements Cloneable { private static native String getTextAttribute(long addr, int symbol); private static native long open(String pattern, String currencySymbol, char decimalSeparator, char digit, String exponentSeparator, char groupingSeparator, - String infinity, String internationalCurrencySymbol, char minusSign, + String infinity, String internationalCurrencySymbol, String minusSign, char monetaryDecimalSeparator, String nan, char patternSeparator, char percent, char perMill, char zeroDigit); private static native Number parse(long addr, String string, ParsePosition position, boolean parseBigDecimal); private static native void setDecimalFormatSymbols(long addr, String currencySymbol, char decimalSeparator, char digit, String exponentSeparator, char groupingSeparator, - String infinity, String internationalCurrencySymbol, char minusSign, + String infinity, String internationalCurrencySymbol, String minusSign, char monetaryDecimalSeparator, String nan, char patternSeparator, char percent, char perMill, char zeroDigit); private static native void setSymbol(long addr, int symbol, String str); diff --git a/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java b/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java index 3ea942d..b23013b 100644 --- a/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java +++ b/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java @@ -52,7 +52,7 @@ public final class RuleBasedCollatorICU implements Cloneable { } public RuleBasedCollatorICU(Locale locale) { - address = NativeCollation.openCollator(locale.toString()); + address = NativeCollation.openCollator(locale); } private RuleBasedCollatorICU(long address) { diff --git a/luni/src/main/java/libcore/icu/TimeZoneNames.java b/luni/src/main/java/libcore/icu/TimeZoneNames.java index 5bb54a1..3413a5d 100644 --- a/luni/src/main/java/libcore/icu/TimeZoneNames.java +++ b/luni/src/main/java/libcore/icu/TimeZoneNames.java @@ -53,15 +53,8 @@ public final class TimeZoneNames { } public static class ZoneStringsCache extends BasicLruCache<Locale, String[][]> { - // De-duplicate the strings (http://b/2672057). - private final HashMap<String, String> internTable = new HashMap<String, String>(); - public ZoneStringsCache() { - // We make room for all the time zones known to the system, since each set of strings - // isn't particularly large (and we remove duplicates), but is currently (Honeycomb) - // really expensive to compute. - // If you change this, you might want to change the scope of the intern table too. - super(availableTimeZoneIds.length); + super(5); // Room for a handful of locales. } @Override protected String[][] create(Locale locale) { @@ -85,11 +78,13 @@ public final class TimeZoneNames { long nativeDuration = nativeEnd - nativeStart; long duration = end - start; System.logI("Loaded time zone names for \"" + locale + "\" in " + duration + "ms" + - " (" + nativeDuration + "ms in ICU)"); + " (" + nativeDuration + "ms in ICU)"); return result; } + // De-duplicate the strings (http://b/2672057). private synchronized void internStrings(String[][] result) { + HashMap<String, String> internTable = new HashMap<String, String>(); for (int i = 0; i < result.length; ++i) { for (int j = 1; j < NAME_COUNT; ++j) { String original = result[i][j]; @@ -162,5 +157,7 @@ public final class TimeZoneNames { return ids.toArray(new String[ids.size()]); } + public static native String getExemplarLocation(String locale, String tz); + private static native void fillZoneStrings(String locale, String[][] result); } diff --git a/luni/src/main/java/libcore/io/BlockGuardOs.java b/luni/src/main/java/libcore/io/BlockGuardOs.java index c61a3cf..b3dc74b 100644 --- a/luni/src/main/java/libcore/io/BlockGuardOs.java +++ b/luni/src/main/java/libcore/io/BlockGuardOs.java @@ -16,14 +16,22 @@ package libcore.io; +import android.system.ErrnoException; +import android.system.StructLinger; +import android.system.StructPollfd; +import android.system.StructStat; +import android.system.StructStatVfs; +import android.util.MutableLong; import dalvik.system.BlockGuard; import dalvik.system.SocketTagger; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; +import static dalvik.system.BlockGuard.DISALLOW_NETWORK; /** * Informs BlockGuard of any activity it should be aware of. @@ -55,9 +63,27 @@ public class BlockGuardOs extends ForwardingOs { return tagSocket(os.accept(fd, peerAddress)); } + @Override public boolean access(String path, int mode) throws ErrnoException { + BlockGuard.getThreadPolicy().onReadFromDisk(); + return os.access(path, mode); + } + + @Override public void chmod(String path, int mode) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.chmod(path, mode); + } + + @Override public void chown(String path, int uid, int gid) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.chown(path, uid, gid); + } + @Override public void close(FileDescriptor fd) throws ErrnoException { try { - if (S_ISSOCK(Libcore.os.fstat(fd).st_mode)) { + // 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 (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 @@ -85,6 +111,16 @@ public class BlockGuardOs extends ForwardingOs { os.connect(fd, address, port); } + @Override public void fchmod(FileDescriptor fd, int mode) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.fchmod(fd, mode); + } + + @Override public void fchown(FileDescriptor fd, int uid, int gid) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.fchown(fd, uid, gid); + } + // TODO: Untag newFd when needed for dup2(FileDescriptor oldFd, int newFd) @Override public void fdatasync(FileDescriptor fd) throws ErrnoException { @@ -92,6 +128,16 @@ public class BlockGuardOs extends ForwardingOs { os.fdatasync(fd); } + @Override public StructStat fstat(FileDescriptor fd) throws ErrnoException { + BlockGuard.getThreadPolicy().onReadFromDisk(); + return os.fstat(fd); + } + + @Override public StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { + BlockGuard.getThreadPolicy().onReadFromDisk(); + return os.fstatvfs(fd); + } + @Override public void fsync(FileDescriptor fd) throws ErrnoException { BlockGuard.getThreadPolicy().onWriteToDisk(); os.fsync(fd); @@ -102,6 +148,36 @@ public class BlockGuardOs extends ForwardingOs { os.ftruncate(fd, length); } + @Override public void lchown(String path, int uid, int gid) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.lchown(path, uid, gid); + } + + @Override public void link(String oldPath, String newPath) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.link(oldPath, newPath); + } + + @Override public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { + BlockGuard.getThreadPolicy().onReadFromDisk(); + return os.lseek(fd, offset, whence); + } + + @Override public StructStat lstat(String path) throws ErrnoException { + BlockGuard.getThreadPolicy().onReadFromDisk(); + return os.lstat(path); + } + + @Override public void mkdir(String path, int mode) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.mkdir(path, mode); + } + + @Override public void mkfifo(String path, int mode) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.mkfifo(path, mode); + } + @Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { BlockGuard.getThreadPolicy().onReadFromDisk(); if ((mode & O_ACCMODE) != O_RDONLY) { @@ -119,37 +195,47 @@ public class BlockGuardOs extends ForwardingOs { return os.poll(fds, timeoutMs); } - @Override public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { + @Override public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.posix_fallocate(fd, offset, length); + } + + @Override public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.pread(fd, buffer, offset); } - @Override public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { + @Override public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.pread(fd, bytes, byteOffset, byteCount, offset); } - @Override public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { + @Override public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.pwrite(fd, buffer, offset); } - @Override public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { + @Override public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.pwrite(fd, bytes, byteOffset, byteCount, offset); } - @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + @Override public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.read(fd, buffer); } - @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + @Override public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.read(fd, bytes, byteOffset, byteCount); } - @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { + @Override public String readlink(String path) throws ErrnoException { + BlockGuard.getThreadPolicy().onReadFromDisk(); + return os.readlink(path); + } + + @Override public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onReadFromDisk(); return os.readv(fd, buffers, offsets, byteCounts); } @@ -164,6 +250,21 @@ public class BlockGuardOs extends ForwardingOs { return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); } + @Override public void remove(String path) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.remove(path); + } + + @Override public void rename(String oldPath, String newPath) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.rename(oldPath, newPath); + } + + @Override public long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + return os.sendfile(outFd, inFd, inOffset, byteCount); + } + @Override public int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws ErrnoException, SocketException { BlockGuard.getThreadPolicy().onNetwork(); return os.sendto(fd, buffer, flags, inetAddress, port); @@ -187,17 +288,32 @@ public class BlockGuardOs extends ForwardingOs { tagSocket(fd2); } - @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + @Override public StructStat stat(String path) throws ErrnoException { + BlockGuard.getThreadPolicy().onReadFromDisk(); + return os.stat(path); + } + + @Override public StructStatVfs statvfs(String path) throws ErrnoException { + BlockGuard.getThreadPolicy().onReadFromDisk(); + return os.statvfs(path); + } + + @Override public void symlink(String oldPath, String newPath) throws ErrnoException { + BlockGuard.getThreadPolicy().onWriteToDisk(); + os.symlink(oldPath, newPath); + } + + @Override public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.write(fd, buffer); } - @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + @Override public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.write(fd, bytes, byteOffset, byteCount); } - @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { + @Override public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { BlockGuard.getThreadPolicy().onWriteToDisk(); return os.writev(fd, buffers, offsets, byteCounts); } diff --git a/luni/src/main/java/org/apache/harmony/luni/util/DeleteOnExit.java b/luni/src/main/java/libcore/io/DeleteOnExit.java index 8fa04fd..36d7948 100644 --- a/luni/src/main/java/org/apache/harmony/luni/util/DeleteOnExit.java +++ b/luni/src/main/java/libcore/io/DeleteOnExit.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.harmony.luni.util; +package libcore.io; import java.io.File; @@ -34,11 +34,6 @@ public class DeleteOnExit extends Thread { private static DeleteOnExit instance; /** - * Our list of files scheduled for deletion. - */ - private ArrayList<String> files = new ArrayList<String>(); - - /** * Returns our singleton instance, creating it if necessary. */ public static synchronized DeleteOnExit getInstance() { @@ -51,12 +46,21 @@ public class DeleteOnExit extends Thread { } /** + * Our list of files scheduled for deletion. + */ + private final ArrayList<String> files = new ArrayList<String>(); + + + private DeleteOnExit() { + } + + /** * Schedules a file for deletion. * * @param filename The file to delete. */ public void addFile(String filename) { - synchronized(files) { + synchronized (files) { if (!files.contains(filename)) { files.add(filename); } diff --git a/luni/src/main/java/libcore/io/ErrnoException.java b/luni/src/main/java/libcore/io/ErrnoException.java deleted file mode 100644 index f484ce9..0000000 --- a/luni/src/main/java/libcore/io/ErrnoException.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2011 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.io; - -import java.io.IOException; -import java.net.SocketException; - -/** - * A checked exception thrown when {@link Os} methods fail. This exception contains the native - * errno value, for comparison against the constants in {@link OsConstants}, should sophisticated - * callers need to adjust their behavior based on the exact failure. - */ -public final class ErrnoException extends Exception { - private final String functionName; - public final int errno; - - public ErrnoException(String functionName, int errno) { - this.functionName = functionName; - this.errno = errno; - } - - public ErrnoException(String functionName, int errno, Throwable cause) { - super(cause); - this.functionName = functionName; - this.errno = errno; - } - - /** - * Converts the stashed function name and errno value to a human-readable string. - * We do this here rather than in the constructor so that callers only pay for - * this if they need it. - */ - @Override public String getMessage() { - String errnoName = OsConstants.errnoName(errno); - if (errnoName == null) { - errnoName = "errno " + errno; - } - String description = Libcore.os.strerror(errno); - return functionName + " failed: " + errnoName + " (" + description + ")"; - } - - public IOException rethrowAsIOException() throws IOException { - IOException newException = new IOException(getMessage()); - newException.initCause(this); - throw newException; - } - - public SocketException rethrowAsSocketException() throws SocketException { - throw new SocketException(getMessage(), this); - } -} diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java index 3800416..bf4b448 100644 --- a/luni/src/main/java/libcore/io/ForwardingOs.java +++ b/luni/src/main/java/libcore/io/ForwardingOs.java @@ -16,14 +16,29 @@ package libcore.io; +import android.system.ErrnoException; +import android.system.GaiException; +import android.system.StructAddrinfo; +import android.system.StructFlock; +import android.system.StructGroupReq; +import android.system.StructGroupSourceReq; +import android.system.StructLinger; +import android.system.StructPasswd; +import android.system.StructPollfd; +import android.system.StructStat; +import android.system.StructStatVfs; +import android.system.StructTimeval; +import android.system.StructUcred; +import android.system.StructUtsname; +import android.util.MutableInt; +import android.util.MutableLong; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; -import libcore.util.MutableInt; -import libcore.util.MutableLong; /** * Subclass this if you want to override some {@link Os} methods but otherwise delegate. @@ -37,6 +52,7 @@ public class ForwardingOs implements Os { public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException { return os.accept(fd, peerAddress); } 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 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); } @@ -58,7 +74,6 @@ public class ForwardingOs implements Os { public void fsync(FileDescriptor fd) throws ErrnoException { os.fsync(fd); } public void ftruncate(FileDescriptor fd, long length) throws ErrnoException { os.ftruncate(fd, length); } public String gai_strerror(int error) { return os.gai_strerror(error); } - public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException { return os.getaddrinfo(node, hints); } public int getegid() { return os.getegid(); } public int geteuid() { return os.geteuid(); } public int getgid() { return os.getgid(); } @@ -85,11 +100,13 @@ public class ForwardingOs implements Os { public boolean isatty(FileDescriptor fd) { return os.isatty(fd); } public void kill(int pid, int signal) throws ErrnoException { os.kill(pid, signal); } public void lchown(String path, int uid, int gid) throws ErrnoException { os.lchown(path, uid, gid); } + public void link(String oldPath, String newPath) throws ErrnoException { os.link(oldPath, newPath); } public void listen(FileDescriptor fd, int backlog) throws ErrnoException { os.listen(fd, backlog); } public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return os.lseek(fd, offset, whence); } public StructStat lstat(String path) throws ErrnoException { return os.lstat(path); } public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException { os.mincore(address, byteCount, vector); } public void mkdir(String path, int mode) throws ErrnoException { os.mkdir(path, mode); } + public void mkfifo(String path, int mode) throws ErrnoException { os.mkfifo(path, mode); } public void mlock(long address, long byteCount) throws ErrnoException { os.mlock(address, byteCount); } public long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return os.mmap(address, byteCount, prot, flags, fd, offset); } public void msync(long address, long byteCount, int flags) throws ErrnoException { os.msync(address, byteCount, flags); } @@ -98,13 +115,16 @@ public class ForwardingOs implements Os { 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 int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return os.poll(fds, timeoutMs); } - public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { return os.pread(fd, buffer, offset); } - public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { return os.pread(fd, bytes, byteOffset, byteCount, offset); } - public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { return os.pwrite(fd, buffer, offset); } - public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { return os.pwrite(fd, bytes, byteOffset, byteCount, offset); } - public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.read(fd, buffer); } - public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.read(fd, bytes, byteOffset, byteCount); } - public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.readv(fd, buffers, offsets, byteCounts); } + 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); }; + public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return os.pread(fd, buffer, offset); } + public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return os.pread(fd, bytes, byteOffset, byteCount, offset); } + public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { return os.pwrite(fd, buffer, offset); } + public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { return os.pwrite(fd, bytes, byteOffset, byteCount, offset); } + public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return os.read(fd, buffer); } + public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return os.read(fd, bytes, byteOffset, byteCount); } + public String readlink(String path) throws ErrnoException { return os.readlink(path); } + public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return os.readv(fd, buffers, offsets, byteCounts); } public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return os.recvfrom(fd, buffer, flags, srcAddress); } public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { return os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress); } public void remove(String path) throws ErrnoException { os.remove(path); } @@ -122,6 +142,7 @@ public class ForwardingOs implements Os { public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptInt(fd, level, option, value); } public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { os.setsockoptIpMreqn(fd, level, option, value); } public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { os.setsockoptGroupReq(fd, level, option, value); } + public void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException { os.setsockoptGroupSourceReq(fd, level, option, value); } public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { os.setsockoptLinger(fd, level, option, value); } public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { os.setsockoptTimeval(fd, level, option, value); } public void setuid(int uid) throws ErrnoException { os.setuid(uid); } @@ -140,7 +161,7 @@ public class ForwardingOs implements Os { public StructUtsname uname() { return os.uname(); } public void unsetenv(String name) throws ErrnoException { os.unsetenv(name); } public int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return os.waitpid(pid, status, options); } - public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { return os.write(fd, buffer); } - public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { return os.write(fd, bytes, byteOffset, byteCount); } - public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException { return os.writev(fd, buffers, offsets, byteCounts); } + public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { return os.write(fd, buffer); } + public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { return os.write(fd, bytes, byteOffset, byteCount); } + public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException { return os.writev(fd, buffers, offsets, byteCounts); } } diff --git a/luni/src/main/java/libcore/io/GaiException.java b/luni/src/main/java/libcore/io/GaiException.java deleted file mode 100644 index 08143dc..0000000 --- a/luni/src/main/java/libcore/io/GaiException.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2011 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.io; - -import java.net.UnknownHostException; -import libcore.io.OsConstants; - -/** - * An unchecked exception thrown when the {@link Os} {@code getaddrinfo} or {@code getnameinfo} - * methods fail. This exception contains the native error value, for comparison against the - * {@code GAI_} constants in {@link OsConstants}, should sophisticated - * callers need to adjust their behavior based on the exact failure. - */ -public final class GaiException extends RuntimeException { - private final String functionName; - public final int error; - - public GaiException(String functionName, int error) { - this.functionName = functionName; - this.error = error; - } - - public GaiException(String functionName, int error, Throwable cause) { - super(cause); - this.functionName = functionName; - this.error = error; - } - - /** - * Converts the stashed function name and error value to a human-readable string. - * We do this here rather than in the constructor so that callers only pay for - * this if they need it. - */ - @Override public String getMessage() { - String gaiName = OsConstants.gaiName(error); - if (gaiName == null) { - gaiName = "GAI_ error " + error; - } - String description = Libcore.os.gai_strerror(error); - return functionName + " failed: " + gaiName + " (" + description + ")"; - } - - public UnknownHostException rethrowAsUnknownHostException(String detailMessage) throws UnknownHostException { - UnknownHostException newException = new UnknownHostException(detailMessage); - newException.initCause(this); - throw newException; - } - - public UnknownHostException rethrowAsUnknownHostException() throws UnknownHostException { - throw rethrowAsUnknownHostException(getMessage()); - } -} diff --git a/luni/src/main/java/libcore/io/IoBridge.java b/luni/src/main/java/libcore/io/IoBridge.java index 28adba1..acc8d4f 100644 --- a/luni/src/main/java/libcore/io/IoBridge.java +++ b/luni/src/main/java/libcore/io/IoBridge.java @@ -16,6 +16,13 @@ package libcore.io; +import android.system.ErrnoException; +import android.system.StructGroupReq; +import android.system.StructGroupSourceReq; +import android.system.StructLinger; +import android.system.StructPollfd; +import android.system.StructTimeval; +import android.util.MutableInt; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; @@ -35,8 +42,7 @@ import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Arrays; -import static libcore.io.OsConstants.*; -import libcore.util.MutableInt; +import static android.system.OsConstants.*; /** * Implements java.io/java.net/java.nio semantics in terms of the underlying POSIX system calls. @@ -71,16 +77,20 @@ public final class IoBridge { public static void bind(FileDescriptor fd, InetAddress address, int port) throws SocketException { - if (address instanceof Inet6Address && ((Inet6Address) address).getScopeId() == 0) { - // Linux won't let you bind a link-local address without a scope id. Find one. - NetworkInterface nif = NetworkInterface.getByInetAddress(address); - if (nif == null) { - throw new SocketException("Can't bind to a link-local address without a scope id: " + address); - } - try { - address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex()); - } catch (UnknownHostException ex) { - throw new AssertionError(ex); // Can't happen. + if (address instanceof Inet6Address) { + Inet6Address inet6Address = (Inet6Address) address; + if (inet6Address.getScopeId() == 0 && inet6Address.isLinkLocalAddress()) { + // Linux won't let you bind a link-local address without a scope id. + // Find one. + NetworkInterface nif = NetworkInterface.getByInetAddress(address); + if (nif == null) { + throw new SocketException("Can't bind to a link-local address without a scope id: " + address); + } + try { + address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex()); + } catch (UnknownHostException ex) { + throw new AssertionError(ex); // Can't happen. + } } } try { @@ -95,9 +105,9 @@ public final class IoBridge { * Connects socket 'fd' to 'inetAddress' on 'port', with no timeout. The lack of a timeout * means this method won't throw SocketTimeoutException. */ - public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException { + public static void connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException { try { - return IoBridge.connect(fd, inetAddress, port, 0); + IoBridge.connect(fd, inetAddress, port, 0); } catch (SocketTimeoutException ex) { throw new AssertionError(ex); // Can't happen for a connect without a timeout. } @@ -107,9 +117,9 @@ public final class IoBridge { * Connects socket 'fd' to 'inetAddress' on 'port', with a the given 'timeoutMs'. * Use timeoutMs == 0 for a blocking connect with no timeout. */ - public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException { + public static void connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException { try { - return connectErrno(fd, inetAddress, port, timeoutMs); + connectErrno(fd, inetAddress, port, timeoutMs); } catch (ErrnoException errnoException) { throw new ConnectException(connectDetail(inetAddress, port, timeoutMs, errnoException), errnoException); } catch (SocketException ex) { @@ -121,11 +131,11 @@ public final class IoBridge { } } - private static boolean connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws ErrnoException, IOException { + private static void connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws ErrnoException, IOException { // With no timeout, just call connect(2) directly. if (timeoutMs == 0) { Libcore.os.connect(fd, inetAddress, port); - return true; + return; } // For connect with a timeout, we: @@ -143,7 +153,7 @@ public final class IoBridge { try { Libcore.os.connect(fd, inetAddress, port); IoUtils.setBlocking(fd, true); // 4. set the socket back to blocking. - return true; // We connected immediately. + return; // We connected immediately. } catch (ErrnoException errnoException) { if (errnoException.errno != EINPROGRESS) { throw errnoException; @@ -160,7 +170,6 @@ public final class IoBridge { } } while (!IoBridge.isConnected(fd, inetAddress, port, timeoutMs, remainingTimeoutMs)); IoUtils.setBlocking(fd, true); // 4. set the socket back to blocking. - return true; // Or we'd have thrown. } private static String connectDetail(InetAddress inetAddress, int port, int timeoutMs, ErrnoException cause) { @@ -174,9 +183,15 @@ public final class IoBridge { return detail; } - public static void closeSocket(FileDescriptor fd) throws IOException { - if (!fd.valid()) { - // Socket.close doesn't throw if you try to close an already-closed socket. + /** + * Closes the supplied file descriptor and sends a signal to any threads are currently blocking. + * In order for the signal to be sent the blocked threads must have registered with + * the AsynchronousCloseMonitor before they entered the blocking operation. + * + * <p>This method is a no-op if passed a {@code null} or already-closed file descriptor. + */ + public static void closeAndSignalBlockedThreads(FileDescriptor fd) throws IOException { + if (fd == null || !fd.valid()) { return; } int intFd = fd.getInt$(); @@ -226,6 +241,10 @@ public final class IoBridge { // Socket options used by java.net but not exposed in SocketOptions. public static final int JAVA_MCAST_JOIN_GROUP = 19; public static final int JAVA_MCAST_LEAVE_GROUP = 20; + public static final int JAVA_MCAST_JOIN_SOURCE_GROUP = 21; + public static final int JAVA_MCAST_LEAVE_SOURCE_GROUP = 22; + public static final int JAVA_MCAST_BLOCK_SOURCE = 23; + public static final int JAVA_MCAST_UNBLOCK_SOURCE = 24; public static final int JAVA_IP_MULTICAST_TTL = 17; /** @@ -369,16 +388,46 @@ public final class IoBridge { return; case IoBridge.JAVA_MCAST_JOIN_GROUP: case IoBridge.JAVA_MCAST_LEAVE_GROUP: + { StructGroupReq groupReq = (StructGroupReq) value; int level = (groupReq.gr_group instanceof Inet4Address) ? IPPROTO_IP : IPPROTO_IPV6; int op = (option == JAVA_MCAST_JOIN_GROUP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP; Libcore.os.setsockoptGroupReq(fd, level, op, groupReq); return; + } + case IoBridge.JAVA_MCAST_JOIN_SOURCE_GROUP: + case IoBridge.JAVA_MCAST_LEAVE_SOURCE_GROUP: + case IoBridge.JAVA_MCAST_BLOCK_SOURCE: + case IoBridge.JAVA_MCAST_UNBLOCK_SOURCE: + { + StructGroupSourceReq groupSourceReq = (StructGroupSourceReq) value; + int level = (groupSourceReq.gsr_group instanceof Inet4Address) + ? IPPROTO_IP : IPPROTO_IPV6; + int op = getGroupSourceReqOp(option); + Libcore.os.setsockoptGroupSourceReq(fd, level, op, groupSourceReq); + return; + } default: throw new SocketException("Unknown socket option: " + option); } } + private static int getGroupSourceReqOp(int javaValue) { + switch (javaValue) { + case IoBridge.JAVA_MCAST_JOIN_SOURCE_GROUP: + return MCAST_JOIN_SOURCE_GROUP; + case IoBridge.JAVA_MCAST_LEAVE_SOURCE_GROUP: + return MCAST_LEAVE_SOURCE_GROUP; + case IoBridge.JAVA_MCAST_BLOCK_SOURCE: + return MCAST_BLOCK_SOURCE; + case IoBridge.JAVA_MCAST_UNBLOCK_SOURCE: + return MCAST_UNBLOCK_SOURCE; + default: + throw new AssertionError( + "Unknown java value for setsocketopt op lookup: " + javaValue); + } + } + /** * java.io only throws FileNotFoundException when opening files, regardless of what actually * went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening @@ -391,12 +440,10 @@ public final class IoBridge { // On Android, we don't want default permissions to allow global access. int mode = ((flags & O_ACCMODE) == O_RDONLY) ? 0 : 0600; fd = Libcore.os.open(path, flags, mode); - if (fd.valid()) { - // Posix open(2) fails with EISDIR only if you ask for write permission. - // Java disallows reading directories too. - if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) { - throw new ErrnoException("open", EISDIR); - } + // Posix open(2) fails with EISDIR only if you ask for write permission. + // Java disallows reading directories too. + if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) { + throw new ErrnoException("open", EISDIR); } return fd; } catch (ErrnoException errnoException) { diff --git a/luni/src/main/java/libcore/io/IoUtils.java b/luni/src/main/java/libcore/io/IoUtils.java index 10ef671..5a19f17 100644 --- a/luni/src/main/java/libcore/io/IoUtils.java +++ b/luni/src/main/java/libcore/io/IoUtils.java @@ -16,6 +16,8 @@ package libcore.io; +import android.system.ErrnoException; +import android.system.StructStat; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; @@ -25,7 +27,7 @@ import java.net.Socket; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Random; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; public final class IoUtils { private static final Random TEMPORARY_DIRECTORY_PRNG = new Random(); diff --git a/luni/src/main/java/libcore/io/Memory.java b/luni/src/main/java/libcore/io/Memory.java index 5743949..e148457 100644 --- a/luni/src/main/java/libcore/io/Memory.java +++ b/luni/src/main/java/libcore/io/Memory.java @@ -151,9 +151,33 @@ public final class Memory { public static native void memmove(Object dstObject, int dstOffset, Object srcObject, int srcOffset, long byteCount); public static native byte peekByte(long address); - public static native int peekInt(long address, boolean swap); - public static native long peekLong(long address, boolean swap); - public static native short peekShort(long address, boolean swap); + + public static int peekInt(long address, boolean swap) { + int result = peekIntNative(address); + if (swap) { + result = Integer.reverseBytes(result); + } + return result; + } + private static native int peekIntNative(long address); + + public static long peekLong(long address, boolean swap) { + long result = peekLongNative(address); + if (swap) { + result = Long.reverseBytes(result); + } + return result; + } + private static native long peekLongNative(long address); + + public static short peekShort(long address, boolean swap) { + short result = peekShortNative(address); + if (swap) { + result = Short.reverseBytes(result); + } + return result; + } + private static native short peekShortNative(long address); public static native void peekByteArray(long address, byte[] dst, int dstOffset, int byteCount); public static native void peekCharArray(long address, char[] dst, int dstOffset, int charCount, boolean swap); @@ -164,9 +188,30 @@ public final class Memory { public static native void peekShortArray(long address, short[] dst, int dstOffset, int shortCount, boolean swap); public static native void pokeByte(long address, byte value); - public static native void pokeInt(long address, int value, boolean swap); - public static native void pokeLong(long address, long value, boolean swap); - public static native void pokeShort(long address, short value, boolean swap); + + public static void pokeInt(long address, int value, boolean swap) { + if (swap) { + value = Integer.reverseBytes(value); + } + pokeIntNative(address, value); + } + private static native void pokeIntNative(long address, int value); + + public static void pokeLong(long address, long value, boolean swap) { + if (swap) { + value = Long.reverseBytes(value); + } + pokeLongNative(address, value); + } + private static native void pokeLongNative(long address, long value); + + public static void pokeShort(long address, short value, boolean swap) { + if (swap) { + value = Short.reverseBytes(value); + } + pokeShortNative(address, value); + } + private static native void pokeShortNative(long address, short value); public static native void pokeByteArray(long address, byte[] src, int offset, int count); public static native void pokeCharArray(long address, char[] src, int offset, int count, boolean swap); diff --git a/luni/src/main/java/libcore/io/MemoryMappedFile.java b/luni/src/main/java/libcore/io/MemoryMappedFile.java index 2d8aa2b..b4cd8fc 100644 --- a/luni/src/main/java/libcore/io/MemoryMappedFile.java +++ b/luni/src/main/java/libcore/io/MemoryMappedFile.java @@ -16,16 +16,16 @@ package libcore.io; +import android.system.ErrnoException; import java.io.FileDescriptor; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteOrder; -import java.nio.NioUtils; import java.nio.channels.FileChannel; -import libcore.io.ErrnoException; +import java.nio.NioUtils; import libcore.io.Libcore; import libcore.io.Memory; -import static libcore.io.OsConstants.*; +import static android.system.OsConstants.*; /** * A memory-mapped file. Use {@link #mmap} to map a file, {@link #close} to unmap a file, diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java index 2b68027..511bb27 100644 --- a/luni/src/main/java/libcore/io/Os.java +++ b/luni/src/main/java/libcore/io/Os.java @@ -16,18 +16,34 @@ package libcore.io; +import android.system.ErrnoException; +import android.system.GaiException; +import android.system.StructAddrinfo; +import android.system.StructFlock; +import android.system.StructGroupReq; +import android.system.StructGroupSourceReq; +import android.system.StructLinger; +import android.system.StructPasswd; +import android.system.StructPollfd; +import android.system.StructStat; +import android.system.StructStatVfs; +import android.system.StructTimeval; +import android.system.StructUcred; +import android.system.StructUtsname; +import android.util.MutableInt; +import android.util.MutableLong; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; -import libcore.util.MutableInt; -import libcore.util.MutableLong; public interface Os { public FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException; 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 chmod(String path, int mode) throws ErrnoException; public void chown(String path, int uid, int gid) throws ErrnoException; @@ -49,7 +65,6 @@ public interface Os { public void fsync(FileDescriptor fd) throws ErrnoException; public void ftruncate(FileDescriptor fd, long length) throws ErrnoException; public String gai_strerror(int error); - public InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException; public int getegid(); public int geteuid(); public int getgid(); @@ -77,11 +92,13 @@ public interface Os { public boolean isatty(FileDescriptor fd); public void kill(int pid, int signal) throws ErrnoException; public void lchown(String path, int uid, int gid) throws ErrnoException; + public void link(String oldPath, String newPath) throws ErrnoException; public void listen(FileDescriptor fd, int backlog) throws ErrnoException; public long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException; public StructStat lstat(String path) throws ErrnoException; public void mincore(long address, long byteCount, byte[] vector) throws ErrnoException; public void mkdir(String path, int mode) throws ErrnoException; + public void mkfifo(String path, int mode) throws ErrnoException; public void mlock(long address, long byteCount) throws ErrnoException; public long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException; public void msync(long address, long byteCount, int flags) throws ErrnoException; @@ -91,13 +108,16 @@ public interface Os { public FileDescriptor[] pipe() 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 int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException; - public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException; - public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException; - public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException; - public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException; - public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException; - public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + public void posix_fallocate(FileDescriptor fd, long offset, long length) throws ErrnoException; + public int prctl(int option, long arg2, long arg3, long arg4, long arg5) throws ErrnoException; + public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException; + public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException; + public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException; + public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException; + public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException; + public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException; + public String readlink(String path) throws ErrnoException; + public int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException; public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException; public int recvfrom(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException; public void remove(String path) throws ErrnoException; @@ -115,6 +135,7 @@ public interface Os { public void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException; public void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException; public void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException; + public void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException; public void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException; public void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException; public void setuid(int uid) throws ErrnoException; @@ -133,7 +154,7 @@ public interface Os { public StructUtsname uname(); public void unsetenv(String name) throws ErrnoException; public int waitpid(int pid, MutableInt status, int options) throws ErrnoException; - public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException; - public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException; - public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException; + public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException; + public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException; } diff --git a/luni/src/main/java/libcore/io/Posix.java b/luni/src/main/java/libcore/io/Posix.java index b99941c..f5eaaa3 100644 --- a/luni/src/main/java/libcore/io/Posix.java +++ b/luni/src/main/java/libcore/io/Posix.java @@ -16,21 +16,37 @@ package libcore.io; +import android.system.ErrnoException; +import android.system.GaiException; +import android.system.StructAddrinfo; +import android.system.StructFlock; +import android.system.StructGroupReq; +import android.system.StructGroupSourceReq; +import android.system.StructLinger; +import android.system.StructPasswd; +import android.system.StructPollfd; +import android.system.StructStat; +import android.system.StructStatVfs; +import android.system.StructTimeval; +import android.system.StructUcred; +import android.system.StructUtsname; +import android.util.MutableInt; +import android.util.MutableLong; import java.io.FileDescriptor; +import java.io.InterruptedIOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.NioUtils; -import libcore.util.MutableInt; -import libcore.util.MutableLong; public final class Posix implements Os { Posix() { } public native FileDescriptor accept(FileDescriptor fd, InetSocketAddress peerAddress) throws ErrnoException, SocketException; 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 chmod(String path, int mode) throws ErrnoException; public native void chown(String path, int uid, int gid) throws ErrnoException; @@ -52,7 +68,6 @@ public final class Posix implements Os { public native void fsync(FileDescriptor fd) throws ErrnoException; public native void ftruncate(FileDescriptor fd, long length) throws ErrnoException; public native String gai_strerror(int error); - public native InetAddress[] getaddrinfo(String node, StructAddrinfo hints) throws GaiException; public native int getegid(); public native int geteuid(); public native int getgid(); @@ -79,11 +94,13 @@ public final class Posix implements Os { public native boolean isatty(FileDescriptor fd); public native void kill(int pid, int signal) throws ErrnoException; public native void lchown(String path, int uid, int gid) throws ErrnoException; + public native void link(String oldPath, String newPath) throws ErrnoException; public native void listen(FileDescriptor fd, int backlog) throws ErrnoException; public native long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException; public native StructStat lstat(String path) throws ErrnoException; public native void mincore(long address, long byteCount, byte[] vector) throws ErrnoException; public native void mkdir(String path, int mode) throws ErrnoException; + public native void mkfifo(String path, int mode) throws ErrnoException; public native void mlock(long address, long byteCount) throws ErrnoException; public native long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException; public native void msync(long address, long byteCount, int flags) throws ErrnoException; @@ -92,43 +109,46 @@ public final class Posix implements Os { public native FileDescriptor open(String path, int flags, int mode) throws ErrnoException; public native FileDescriptor[] pipe() throws ErrnoException; public native int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException; - public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) 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; + public int pread(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { if (buffer.isDirect()) { return preadBytes(fd, buffer, buffer.position(), buffer.remaining(), offset); } else { return preadBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), offset); } } - public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { + public int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return preadBytes(fd, bytes, byteOffset, byteCount, offset); } - private native int preadBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException; - public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException { + private native int preadBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException; + public int pwrite(FileDescriptor fd, ByteBuffer buffer, long offset) throws ErrnoException, InterruptedIOException { if (buffer.isDirect()) { return pwriteBytes(fd, buffer, buffer.position(), buffer.remaining(), offset); } else { return pwriteBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining(), offset); } } - public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException { + public int pwrite(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return pwriteBytes(fd, bytes, byteOffset, byteCount, offset); } - private native int pwriteBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException; - public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + private native int pwriteBytes(FileDescriptor fd, Object buffer, int bufferOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException; + public int read(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { if (buffer.isDirect()) { return readBytes(fd, buffer, buffer.position(), buffer.remaining()); } else { return readBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining()); } } - public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + public int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return readBytes(fd, bytes, byteOffset, byteCount); } - private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException; - public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + private native int readBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException; + public native String readlink(String path) throws ErrnoException; + public native int readv(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException; public int recvfrom(FileDescriptor fd, ByteBuffer buffer, int flags, InetSocketAddress srcAddress) throws ErrnoException, SocketException { if (buffer.isDirect()) { return recvfromBytes(fd, buffer, buffer.position(), buffer.remaining(), flags, srcAddress); @@ -166,6 +186,7 @@ public final class Posix implements Os { public native void setsockoptInt(FileDescriptor fd, int level, int option, int value) throws ErrnoException; public native void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException; public native void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException; + public native void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException; public native void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException; public native void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException; public native void setuid(int uid) throws ErrnoException; @@ -190,17 +211,17 @@ public final class Posix implements Os { public native StructUtsname uname(); public native void unsetenv(String name) throws ErrnoException; public native int waitpid(int pid, MutableInt status, int options) throws ErrnoException; - public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException { + public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException { if (buffer.isDirect()) { return writeBytes(fd, buffer, buffer.position(), buffer.remaining()); } else { return writeBytes(fd, NioUtils.unsafeArray(buffer), NioUtils.unsafeArrayOffset(buffer) + buffer.position(), buffer.remaining()); } } - public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException { + public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException { // This indirection isn't strictly necessary, but ensures that our public interface is type safe. return writeBytes(fd, bytes, byteOffset, byteCount); } - private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException; - public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException; + private native int writeBytes(FileDescriptor fd, Object buffer, int offset, int byteCount) throws ErrnoException, InterruptedIOException; + public native int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException; } diff --git a/luni/src/main/java/libcore/io/Streams.java b/luni/src/main/java/libcore/io/Streams.java index cbad4a4..1f78edd 100644 --- a/luni/src/main/java/libcore/io/Streams.java +++ b/luni/src/main/java/libcore/io/Streams.java @@ -135,8 +135,9 @@ public final class Streams { } /** - * Call {@code in.read()} repeatedly until either the stream is exhausted or - * {@code byteCount} bytes have been read. + * Skip <b>at most</b> {@code byteCount} bytes from {@code in} by calling read + * repeatedly until either the stream is exhausted or we read fewer bytes than + * we ask for. * * <p>This method reuses the skip buffer but is careful to never use it at * the same time that another stream is using it. Otherwise streams that use diff --git a/luni/src/main/java/libcore/io/StructPasswd.java b/luni/src/main/java/libcore/io/StructPasswd.java deleted file mode 100644 index 6f5e058..0000000 --- a/luni/src/main/java/libcore/io/StructPasswd.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2011 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.io; - -/** - * Information returned by getpwnam(3) and getpwuid(3). Corresponds to C's - * {@code struct passwd} from - * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pwd.h.html"><pwd.h></a> - */ -public final class StructPasswd { - public String pw_name; - public int pw_uid; /* uid_t */ - public int pw_gid; /* gid_t */ - public String pw_dir; - public String pw_shell; - - public StructPasswd(String pw_name, int pw_uid, int pw_gid, String pw_dir, String pw_shell) { - this.pw_name = pw_name; - this.pw_uid = pw_uid; - this.pw_gid = pw_gid; - this.pw_dir = pw_dir; - this.pw_shell = pw_shell; - } -} diff --git a/luni/src/main/java/libcore/io/StructPollfd.java b/luni/src/main/java/libcore/io/StructPollfd.java deleted file mode 100644 index c659d6e..0000000 --- a/luni/src/main/java/libcore/io/StructPollfd.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2011 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.io; - -import java.io.FileDescriptor; - -/** - * Corresponds to C's {@code struct pollfd} from - * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/poll.h.html"><poll.h></a> - */ -public final class StructPollfd { - /** The file descriptor to poll. */ - public FileDescriptor fd; - - /** - * The events we're interested in. POLLIN corresponds to being in select(2)'s read fd set, - * POLLOUT to the write fd set. - */ - public short events; - - /** The events that actually happened. */ - public short revents; - - /** - * A non-standard extension that lets callers conveniently map back to the object - * their fd belongs to. This is used by Selector, for example, to associate each - * FileDescriptor with the corresponding SelectionKey. - */ - public Object userData; - - @Override public String toString() { - return "StructPollfd[fd=" + fd + ",events=" + events + ",revents=" + revents + "]"; - } -} diff --git a/luni/src/main/java/libcore/io/StructStat.java b/luni/src/main/java/libcore/io/StructStat.java deleted file mode 100644 index 05ecca7..0000000 --- a/luni/src/main/java/libcore/io/StructStat.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2011 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.io; - -/** - * File information returned by fstat(2), lstat(2), and stat(2). Corresponds to C's - * {@code struct stat} from - * <a href="http://www.opengroup.org/onlinepubs/000095399/basedefs/sys/stat.h.html"><stat.h></a> - */ -public final class StructStat { - /** Device ID of device containing file. */ - public final long st_dev; /*dev_t*/ - - /** File serial number (inode). */ - public final long st_ino; /*ino_t*/ - - /** Mode (permissions) of file. */ - public final int st_mode; /*mode_t*/ - - /** Number of hard links to the file. */ - public final long st_nlink; /*nlink_t*/ - - /** User ID of file. */ - public final int st_uid; /*uid_t*/ - - /** Group ID of file. */ - public final int st_gid; /*gid_t*/ - - /** Device ID (if file is character or block special). */ - public final long st_rdev; /*dev_t*/ - - /** - * For regular files, the file size in bytes. - * For symbolic links, the length in bytes of the pathname contained in the symbolic link. - * For a shared memory object, the length in bytes. - * For a typed memory object, the length in bytes. - * For other file types, the use of this field is unspecified. - */ - public final long st_size; /*off_t*/ - - /** Time of last access. */ - public final long st_atime; /*time_t*/ - - /** Time of last data modification. */ - public final long st_mtime; /*time_t*/ - - /** Time of last status change. */ - public final long st_ctime; /*time_t*/ - - /** - * A file system-specific preferred I/O block size for this object. - * For some file system types, this may vary from file to file. - */ - public final long st_blksize; /*blksize_t*/ - - /** Number of blocks allocated for this object. */ - public final long st_blocks; /*blkcnt_t*/ - - StructStat(long st_dev, long st_ino, int st_mode, long st_nlink, int st_uid, int st_gid, - long st_rdev, long st_size, long st_atime, long st_mtime, long st_ctime, - long st_blksize, long st_blocks) { - this.st_dev = st_dev; - this.st_ino = st_ino; - this.st_mode = st_mode; - this.st_nlink = st_nlink; - this.st_uid = st_uid; - this.st_gid = st_gid; - this.st_rdev = st_rdev; - this.st_size = st_size; - this.st_atime = st_atime; - this.st_mtime = st_mtime; - this.st_ctime = st_ctime; - this.st_blksize = st_blksize; - this.st_blocks = st_blocks; - } -} diff --git a/luni/src/main/java/libcore/io/StructUtsname.java b/luni/src/main/java/libcore/io/StructUtsname.java deleted file mode 100644 index e6a8e42..0000000 --- a/luni/src/main/java/libcore/io/StructUtsname.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2011 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.io; - -/** - * Information returned by uname(2). Corresponds to C's - * {@code struct utsname} from - * <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_utsname.h.html"><sys/utsname.h></a> - */ -public final class StructUtsname { - /** The OS name, such as "Linux". */ - public final String sysname; - - /** The machine's unqualified name on some implementation-defined network. */ - public final String nodename; - - /** The OS release, such as "2.6.35-27-generic". */ - public final String release; - - /** The OS version, such as "#48-Ubuntu SMP Tue Feb 22 20:25:29 UTC 2011". */ - public final String version; - - /** The machine architecture, such as "armv7l" or "x86_64". */ - public final String machine; - - StructUtsname(String sysname, String nodename, String release, String version, String machine) { - this.sysname = sysname; - this.nodename = nodename; - this.release = release; - this.version = version; - this.machine = machine; - } -} diff --git a/luni/src/main/java/libcore/net/MimeUtils.java b/luni/src/main/java/libcore/net/MimeUtils.java index aaa5670..a5a1469 100644 --- a/luni/src/main/java/libcore/net/MimeUtils.java +++ b/luni/src/main/java/libcore/net/MimeUtils.java @@ -49,14 +49,13 @@ public final class MimeUtils { add("application/andrew-inset", "ez"); add("application/dsptype", "tsp"); - add("application/futuresplash", "spl"); add("application/hta", "hta"); add("application/mac-binhex40", "hqx"); - add("application/mac-compactpro", "cpt"); add("application/mathematica", "nb"); add("application/msaccess", "mdb"); add("application/oda", "oda"); add("application/ogg", "ogg"); + add("application/ogg", "oga"); add("application/pdf", "pdf"); add("application/pgp-keys", "key"); add("application/pgp-signature", "pgp"); @@ -133,14 +132,15 @@ public final class MimeUtils { add("application/x-dms", "dms"); add("application/x-doom", "wad"); add("application/x-dvi", "dvi"); - add("application/x-flac", "flac"); add("application/x-font", "pfa"); add("application/x-font", "pfb"); add("application/x-font", "gsf"); add("application/x-font", "pcf"); add("application/x-font", "pcf.Z"); add("application/x-freemind", "mm"); + // application/futuresplash isn't IANA, so application/x-futuresplash should come first. add("application/x-futuresplash", "spl"); + add("application/futuresplash", "spl"); add("application/x-gnumeric", "gnumeric"); add("application/x-go-sgf", "sgf"); add("application/x-graphing-calculator", "gcf"); @@ -212,11 +212,19 @@ public final class MimeUtils { add("application/x-xfig", "fig"); add("application/xhtml+xml", "xhtml"); add("audio/3gpp", "3gpp"); + add("audio/aac", "aac"); + add("audio/aac-adts", "aac"); add("audio/amr", "amr"); + add("audio/amr-wb", "awb"); add("audio/basic", "snd"); + add("audio/flac", "flac"); + add("application/x-flac", "flac"); + add("audio/imelody", "imy"); add("audio/midi", "mid"); add("audio/midi", "midi"); + add("audio/midi", "ota"); add("audio/midi", "kar"); + add("audio/midi", "rtttl"); add("audio/midi", "xmf"); add("audio/mobile-xmf", "mxmf"); // add ".mp3" first so it will be the default for guessExtensionFromMimeType @@ -231,6 +239,7 @@ public final class MimeUtils { add("audio/x-aiff", "aiff"); add("audio/x-aiff", "aifc"); add("audio/x-gsm", "gsm"); + add("audio/x-matroska", "mka"); add("audio/x-mpegurl", "m3u"); add("audio/x-ms-wma", "wma"); add("audio/x-ms-wax", "wax"); @@ -241,8 +250,12 @@ public final class MimeUtils { add("audio/x-scpls", "pls"); add("audio/x-sd2", "sd2"); add("audio/x-wav", "wav"); + // image/bmp isn't IANA, so image/x-ms-bmp should come first. + add("image/x-ms-bmp", "bmp"); add("image/bmp", "bmp"); add("image/gif", "gif"); + // image/ico isn't IANA, so image/x-icon should come first. + add("image/x-icon", "ico"); add("image/ico", "cur"); add("image/ico", "ico"); add("image/ief", "ief"); @@ -258,15 +271,14 @@ public final class MimeUtils { add("image/vnd.djvu", "djvu"); add("image/vnd.djvu", "djv"); add("image/vnd.wap.wbmp", "wbmp"); + add("image/webp", "webp"); add("image/x-cmu-raster", "ras"); add("image/x-coreldraw", "cdr"); add("image/x-coreldrawpattern", "pat"); add("image/x-coreldrawtemplate", "cdt"); add("image/x-corelphotopaint", "cpt"); - add("image/x-icon", "ico"); add("image/x-jg", "art"); add("image/x-jng", "jng"); - add("image/x-ms-bmp", "bmp"); add("image/x-photoshop", "psd"); add("image/x-portable-anymap", "pnm"); add("image/x-portable-bitmap", "pbm"); @@ -298,7 +310,6 @@ public final class MimeUtils { add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint add("text/richtext", "rtx"); add("text/rtf", "rtf"); - add("text/texmacs", "ts"); add("text/text", "phps"); add("text/tab-separated-values", "tsv"); add("text/xml", "xml"); @@ -334,12 +345,15 @@ public final class MimeUtils { add("text/x-vcard", "vcf"); add("video/3gpp", "3gpp"); add("video/3gpp", "3gp"); - add("video/3gpp", "3g2"); + add("video/3gpp2", "3gpp2"); + add("video/3gpp2", "3g2"); + add("video/avi", "avi"); add("video/dl", "dl"); add("video/dv", "dif"); add("video/dv", "dv"); add("video/fli", "fli"); add("video/m4v", "m4v"); + add("video/mp2ts", "ts"); add("video/mpeg", "mpeg"); add("video/mpeg", "mpg"); add("video/mpeg", "mpe"); @@ -348,8 +362,10 @@ public final class MimeUtils { add("video/quicktime", "qt"); add("video/quicktime", "mov"); add("video/vnd.mpegurl", "mxu"); + add("video/webm", "webm"); add("video/x-la-asf", "lsf"); add("video/x-la-asf", "lsx"); + add("video/x-matroska", "mkv"); add("video/x-mng", "mng"); add("video/x-ms-asf", "asf"); add("video/x-ms-asf", "asx"); @@ -357,7 +373,6 @@ public final class MimeUtils { add("video/x-ms-wmv", "wmv"); add("video/x-ms-wmx", "wmx"); add("video/x-ms-wvx", "wvx"); - add("video/x-msvideo", "avi"); add("video/x-sgi-movie", "movie"); add("video/x-webex", "wrf"); add("x-conference/x-cooltalk", "ice"); @@ -366,18 +381,17 @@ public final class MimeUtils { } private static void add(String mimeType, String extension) { - // - // if we have an existing x --> y mapping, we do not want to - // override it with another mapping x --> ? - // this is mostly because of the way the mime-type map below - // is constructed (if a mime type maps to several extensions - // the first extension is considered the most popular and is - // added first; we do not want to overwrite it later). - // + // If we have an existing x -> y mapping, we do not want to + // override it with another mapping x -> y2. + // If a mime type maps to several extensions + // the first extension added is considered the most popular + // so we do not want to overwrite it later. if (!mimeTypeToExtensionMap.containsKey(mimeType)) { mimeTypeToExtensionMap.put(mimeType, extension); } - extensionToMimeTypeMap.put(extension, mimeType); + if (!extensionToMimeTypeMap.containsKey(extension)) { + extensionToMimeTypeMap.put(extension, mimeType); + } } private static InputStream getContentTypesPropertiesStream() { diff --git a/luni/src/main/java/libcore/net/RawSocket.java b/luni/src/main/java/libcore/net/RawSocket.java deleted file mode 100644 index 08a7d09..0000000 --- a/luni/src/main/java/libcore/net/RawSocket.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.net; - -import dalvik.system.CloseGuard; -import java.io.Closeable; -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.SocketException; -import java.util.Arrays; -import libcore.io.IoBridge; - -/** - * This class allows raw L2 packets to be sent and received via the - * specified network interface. The receive-side implementation is - * restricted to UDP packets for efficiency. - * - * @hide - */ -public class RawSocket implements Closeable { - /** - * Ethernet IP protocol type, part of the L2 header of IP packets. - */ - public static final short ETH_P_IP = (short) 0x0800; - - /** - * Ethernet ARP protocol type, part of the L2 header of ARP packets. - */ - public static final short ETH_P_ARP = (short) 0x0806; - - private static native void create(FileDescriptor fd, short - protocolType, String interfaceName) - throws SocketException; - private static native int sendPacket(FileDescriptor fd, - String interfaceName, short protocolType, byte[] destMac, byte[] packet, - int offset, int byteCount); - private static native int recvPacket(FileDescriptor fd, byte[] packet, - int offset, int byteCount, int destPort, int timeoutMillis); - - private final FileDescriptor fd; - private final String mInterfaceName; - private final short mProtocolType; - private final CloseGuard guard = CloseGuard.get(); - - /** - * Creates a socket on the specified interface. - */ - public RawSocket(String interfaceName, short protocolType) - throws SocketException { - mInterfaceName = interfaceName; - mProtocolType = protocolType; - fd = new FileDescriptor(); - create(fd, mProtocolType, mInterfaceName); - guard.open("close"); - } - - /** - * Reads a raw packet into the specified buffer, with the - * specified timeout. If the destPort is -1, then the IP - * destination port is not verified, otherwise only packets - * destined for the specified UDP port are returned. Returns the - * length actually read. No indication of overflow is signaled. - * The packet data will start at the IP header (EthernetII - * dest/source/type headers are removed). - */ - public int read(byte[] packet, int offset, int byteCount, int destPort, - int timeoutMillis) { - if (packet == null) { - throw new NullPointerException("packet == null"); - } - - Arrays.checkOffsetAndCount(packet.length, offset, byteCount); - - if (destPort > 65535) { - throw new IllegalArgumentException("Port out of range: " - + destPort); - } - - return recvPacket(fd, packet, offset, byteCount, destPort, - timeoutMillis); - } - - /** - * Writes a raw packet to the desired interface. A L2 header will - * be added which includes the specified destination address, our - * source MAC, and the specified protocol type. The caller is responsible - * for computing correct IP-header and payload checksums. - */ - public int write(byte[] destMac, byte[] packet, int offset, int byteCount) { - if (destMac == null) { - throw new NullPointerException("destMac == null"); - } - - if (packet == null) { - throw new NullPointerException("packet == null"); - } - - Arrays.checkOffsetAndCount(packet.length, offset, byteCount); - - if (destMac.length != 6) { - throw new IllegalArgumentException("MAC length must be 6: " - + destMac.length); - } - - return sendPacket(fd, mInterfaceName, mProtocolType, destMac, packet, - offset, byteCount); - } - - /** - * Closes the socket. After this method is invoked, subsequent - * read/write operations will fail. - */ - public void close() throws IOException { - guard.close(); - IoBridge.closeSocket(fd); - } - - @Override protected void finalize() throws Throwable { - try { - if (guard != null) { - guard.warnIfOpen(); - } - close(); - } finally { - super.finalize(); - } - } -} diff --git a/luni/src/main/java/libcore/net/event/NetworkEventDispatcher.java b/luni/src/main/java/libcore/net/event/NetworkEventDispatcher.java new file mode 100644 index 0000000..d1c7c21 --- /dev/null +++ b/luni/src/main/java/libcore/net/event/NetworkEventDispatcher.java @@ -0,0 +1,79 @@ +/* + * 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.event; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * A singleton used to dispatch network events to registered listeners. + */ +public class NetworkEventDispatcher { + + private static final NetworkEventDispatcher instance = new NetworkEventDispatcher(); + + private final List<NetworkEventListener> listeners = + new CopyOnWriteArrayList<NetworkEventListener>(); + + /** + * Returns the shared {@link NetworkEventDispatcher} instance. + */ + public static NetworkEventDispatcher getInstance() { + return instance; + } + + /** Visible for testing. Use {@link #getInstance()} instead. */ + protected NetworkEventDispatcher() { + } + + /** + * Registers a listener to be notified when network events occur. + * It can be deregistered using {@link #removeListener(NetworkEventListener)} + */ + public void addListener(NetworkEventListener toAdd) { + if (toAdd == null) { + throw new NullPointerException("toAdd == null"); + } + listeners.add(toAdd); + } + + /** + * De-registers a listener previously added with {@link #addListener(NetworkEventListener)}. If + * the listener was not previously registered this is a no-op. + */ + public void removeListener(NetworkEventListener toRemove) { + for (NetworkEventListener listener : listeners) { + if (listener == toRemove) { + listeners.remove(listener); + return; + } + } + } + + /** + * Notifies registered listeners of a network configuration change. + */ + public void onNetworkConfigurationChanged() { + for (NetworkEventListener listener : listeners) { + try { + listener.onNetworkConfigurationChanged(); + } catch (RuntimeException e) { + System.logI("Exception thrown during network event propagation", e); + } + } + } +} diff --git a/luni/src/main/java/libcore/net/event/NetworkEventListener.java b/luni/src/main/java/libcore/net/event/NetworkEventListener.java new file mode 100644 index 0000000..73b9f88 --- /dev/null +++ b/luni/src/main/java/libcore/net/event/NetworkEventListener.java @@ -0,0 +1,27 @@ +/* + * 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.event; + +/** + * A base class for objects interested in network events. + */ +public class NetworkEventListener { + + public void onNetworkConfigurationChanged() { + // no-op + } +} diff --git a/luni/src/main/java/libcore/net/url/FileURLConnection.java b/luni/src/main/java/libcore/net/url/FileURLConnection.java index b4654cd..43eaa7d 100644 --- a/luni/src/main/java/libcore/net/url/FileURLConnection.java +++ b/luni/src/main/java/libcore/net/url/FileURLConnection.java @@ -28,6 +28,11 @@ import java.io.InputStream; import java.io.PrintStream; import java.net.URL; import java.net.URLConnection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; import libcore.net.UriCodec; /** @@ -38,17 +43,45 @@ import libcore.net.UriCodec; */ public class FileURLConnection extends URLConnection { + private static final Comparator<String> HEADER_COMPARATOR = new Comparator<String>() { + @Override + public int compare(String a, String b) { + if (a == b) { + return 0; + } else if (a == null) { + return -1; + } else if (b == null) { + return 1; + } else { + return String.CASE_INSENSITIVE_ORDER.compare(a, b); + } + } + }; + private String filename; private InputStream is; - private int length = -1; + private long length = -1; + + private long lastModified = -1; private boolean isDir; private FilePermission permission; /** + * A set of three key value pairs representing the headers we support. + */ + private final String[] headerKeysAndValues; + + private static final int CONTENT_TYPE_VALUE_IDX = 1; + private static final int CONTENT_LENGTH_VALUE_IDX = 3; + private static final int LAST_MODIFIED_VALUE_IDX = 5; + + private Map<String, List<String>> headerFields; + + /** * Creates an instance of <code>FileURLConnection</code> for establishing * a connection to the file pointed by this <code>URL<code> * @@ -61,6 +94,10 @@ public class FileURLConnection extends URLConnection { filename = ""; } filename = UriCodec.decode(filename); + headerKeysAndValues = new String[] { + "content-type", null, + "content-length", null, + "last-modified", null }; } /** @@ -74,27 +111,122 @@ public class FileURLConnection extends URLConnection { @Override public void connect() throws IOException { File f = new File(filename); + IOException error = null; if (f.isDirectory()) { isDir = true; is = getDirectoryListing(f); // use -1 for the contentLength + lastModified = f.lastModified(); + headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = "text/html"; } else { - is = new BufferedInputStream(new FileInputStream(f)); - long lengthAsLong = f.length(); - length = lengthAsLong <= Integer.MAX_VALUE ? (int) lengthAsLong : Integer.MAX_VALUE; + try { + is = new BufferedInputStream(new FileInputStream(f)); + } catch (IOException ioe) { + error = ioe; + } + + if (error == null) { + length = f.length(); + lastModified = f.lastModified(); + headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = getContentTypeForPlainFiles(); + } else { + headerKeysAndValues[CONTENT_TYPE_VALUE_IDX] = "content/unknown"; + } } + + headerKeysAndValues[CONTENT_LENGTH_VALUE_IDX] = String.valueOf(length); + headerKeysAndValues[LAST_MODIFIED_VALUE_IDX] = String.valueOf(lastModified); + connected = true; + if (error != null) { + throw error; + } + } + + @Override + public String getHeaderField(String key) { + if (!connected) { + try { + connect(); + } catch (IOException ioe) { + return null; + } + } + + for (int i = 0; i < headerKeysAndValues.length; i += 2) { + if (headerKeysAndValues[i].equalsIgnoreCase(key)) { + return headerKeysAndValues[i + 1]; + } + } + + return null; + } + + @Override + public String getHeaderFieldKey(int position) { + if (!connected) { + try { + connect(); + } catch (IOException ioe) { + return null; + } + } + + if (position < 0 || position > headerKeysAndValues.length / 2) { + return null; + } + + return headerKeysAndValues[position * 2]; + } + + @Override + public String getHeaderField(int position) { + if (!connected) { + try { + connect(); + } catch (IOException ioe) { + return null; + } + } + + if (position < 0 || position > headerKeysAndValues.length / 2) { + return null; + } + + return headerKeysAndValues[(position * 2) + 1]; + } + + @Override + public Map<String, List<String>> getHeaderFields() { + if (headerFields == null) { + final TreeMap<String, List<String>> headerFieldsMap = new TreeMap<>(HEADER_COMPARATOR); + + for (int i = 0; i < headerKeysAndValues.length; i+=2) { + headerFieldsMap.put(headerKeysAndValues[i], + Collections.singletonList(headerKeysAndValues[i + 1])); + } + + headerFields = Collections.unmodifiableMap(headerFieldsMap); + } + + return headerFields; } /** - * Returns the length of the file in bytes. - * - * @return the length of the file - * - * @see #getContentType() + * Returns the length of the file in bytes, or {@code -1} if the length cannot be + * represented as an {@code int}. See {@link #getContentLengthLong()} for a method that can + * handle larger files. */ @Override public int getContentLength() { + long length = getContentLengthLong(); + return length <= Integer.MAX_VALUE ? (int) length : -1; + } + + /** + * Returns the length of the file in bytes. + */ + private long getContentLengthLong() { try { if (!connected) { connect(); @@ -113,16 +245,11 @@ public class FileURLConnection extends URLConnection { */ @Override public String getContentType() { - try { - if (!connected) { - connect(); - } - } catch (IOException e) { - return "content/unknown"; - } - if (isDir) { - return "text/plain"; - } + // The content-type header field is always at position 0. + return getHeaderField(0); + } + + private String getContentTypeForPlainFiles() { String result = guessContentTypeFromName(url.getFile()); if (result != null) { return result; diff --git a/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java b/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java index 762f0e2..b01a20a 100644 --- a/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java +++ b/luni/src/main/java/libcore/net/url/JarURLConnectionImpl.java @@ -258,12 +258,10 @@ public class JarURLConnectionImpl extends JarURLConnection { } /** - * Returns the content length of the resource. Test cases reveal that if the - * URL is referring to a Jar file, this method answers a content-length - * returned by URLConnection. For jar entry it should return it's size. - * Otherwise, it will return -1. - * - * @return the content length + * Returns the content length of the resource. Test cases reveal that if the URL is referring to + * a Jar file, this method answers a content-length returned by URLConnection. For a jar entry + * it returns the entry's size if it can be represented as an {@code int}. Otherwise, it will + * return -1. */ @Override public int getContentLength() { diff --git a/luni/src/main/java/libcore/reflect/AnnotationAccess.java b/luni/src/main/java/libcore/reflect/AnnotationAccess.java index 4e34284..2a72c18 100644 --- a/luni/src/main/java/libcore/reflect/AnnotationAccess.java +++ b/luni/src/main/java/libcore/reflect/AnnotationAccess.java @@ -16,7 +16,6 @@ package libcore.reflect; -import com.android.dex.ClassDef; import com.android.dex.Dex; import com.android.dex.EncodedValueReader; import com.android.dex.FieldId; @@ -167,7 +166,7 @@ public final class AnnotationAccess { */ public static <A extends Annotation> A getDeclaredAnnotation( AnnotatedElement element, Class<A> annotationClass) { - com.android.dex.Annotation a = getMethodAnnotation(element, annotationClass); + com.android.dex.Annotation a = getAnnotation(element, annotationClass); return a != null ? toAnnotationInstance(getDexClass(element), annotationClass, a) : null; @@ -178,29 +177,29 @@ public final class AnnotationAccess { */ public static boolean isDeclaredAnnotationPresent( AnnotatedElement element, Class<? extends Annotation> annotationClass) { - return getMethodAnnotation(element, annotationClass) != null; + return getAnnotation(element, annotationClass) != null; } - private static com.android.dex.Annotation getMethodAnnotation( + private static com.android.dex.Annotation getAnnotation( AnnotatedElement element, Class<? extends Annotation> annotationClass) { - Class<?> dexClass = getDexClass(element); - Dex dex = dexClass.getDex(); - int annotationTypeIndex = getTypeIndex(dex, annotationClass); - if (annotationTypeIndex == -1) { - return null; // The dex file doesn't use this annotation. - } - int annotationSetOffset = getAnnotationSetOffset(element); if (annotationSetOffset == 0) { return null; // no annotation } + Class<?> dexClass = getDexClass(element); + Dex dex = dexClass.getDex(); Dex.Section setIn = dex.open(annotationSetOffset); // annotation_set_item + String annotationInternalName = InternalNames.getInternalName(annotationClass); for (int i = 0, size = setIn.readInt(); i < size; i++) { int annotationOffset = setIn.readInt(); Dex.Section annotationIn = dex.open(annotationOffset); // annotation_item + // The internal string name of the annotation is compared here and deliberately not + // the value of annotationClass.getTypeIndex(). The annotationClass may have been + // defined by a different dex file, which would make the indexes incomparable. com.android.dex.Annotation candidate = annotationIn.readAnnotation(); - if (candidate.getTypeIndex() == annotationTypeIndex) { + String candidateInternalName = dex.typeNames().get(candidate.getTypeIndex()); + if (candidateInternalName.equals(annotationInternalName)) { return candidate; } } @@ -228,19 +227,22 @@ public final class AnnotationAccess { int methodsSize = directoryIn.readInt(); directoryIn.readInt(); // parameters size - int fieldIndex = element instanceof Field ? ((Field) element).getDexFieldIndex() : -1; - for (int i = 0; i < fieldsSize; i++) { - int candidateFieldIndex = directoryIn.readInt(); - int annotationSetOffset = directoryIn.readInt(); - if (candidateFieldIndex == fieldIndex) { - return annotationSetOffset; - } - } - // we must read all fields prior to methods, if we were searching for a field then we missed if (element instanceof Field) { + int fieldIndex = ((Field) element).getDexFieldIndex(); + for (int i = 0; i < fieldsSize; i++) { + int candidateFieldIndex = directoryIn.readInt(); + int annotationSetOffset = directoryIn.readInt(); + if (candidateFieldIndex == fieldIndex) { + return annotationSetOffset; + } + } + // if we were searching for a field then we missed return 0; } + // Skip through the fields without reading them and look for constructors or methods. + directoryIn.skip(8 * fieldsSize); + int methodIndex= element instanceof Method ? ((Method) element).getDexMethodIndex() : ((Constructor<?>) element).getDexMethodIndex(); for (int i = 0; i < methodsSize; i++) { @@ -265,23 +267,6 @@ public final class AnnotationAccess { : ((Member) element).getDeclaringClass(); } - public static int getFieldIndex(Class<?> declaringClass, Class<?> type, String name) { - Dex dex = declaringClass.getDex(); - int declaringClassIndex = getTypeIndex(dex, declaringClass); - int typeIndex = getTypeIndex(dex, type); - int nameIndex = dex.findStringIndex(name); - FieldId fieldId = new FieldId(dex, declaringClassIndex, typeIndex, nameIndex); - return dex.findFieldIndex(fieldId); - } - - public static int getMethodIndex(Class<?> declaringClass, String name, int protoIndex) { - Dex dex = declaringClass.getDex(); - int declaringClassIndex = getTypeIndex(dex, declaringClass); - int nameIndex = dex.findStringIndex(name); - MethodId methodId = new MethodId(dex, declaringClassIndex, protoIndex, nameIndex); - return dex.findMethodIndex(methodId); - } - /** * Returns the parameter annotations on {@code member}. */ @@ -354,6 +339,8 @@ public final class AnnotationAccess { */ Class<?> annotationClass = method.getDeclaringClass(); + // All lookups of type and string indexes are within the Dex that declares the annotation so + // the indexes can be compared directly. Dex dex = annotationClass.getDex(); EncodedValueReader reader = getOnlyAnnotationValue( dex, annotationClass, "Ldalvik/annotation/AnnotationDefault;"); @@ -362,7 +349,7 @@ public final class AnnotationAccess { } int fieldCount = reader.readAnnotation(); - if (reader.getAnnotationType() != getTypeIndex(dex, annotationClass)) { + if (reader.getAnnotationType() != annotationClass.getDexTypeIndex()) { throw new AssertionError("annotation value type != annotation class"); } @@ -384,7 +371,7 @@ public final class AnnotationAccess { * Returns the class of which {@code c} is a direct member. If {@code c} is * defined in a method or constructor, this is not transitive. */ - public static Class<?> getDeclaringClass(Class<?> c) { + public static Class<?> getEnclosingClass(Class<?> c) { /* * public class Bar { * @EnclosingClass(value=Bar) @@ -537,22 +524,6 @@ public final class AnnotationAccess { * was derived. */ - /** Find dex's type index for the class c */ - private static int getTypeIndex(Dex dex, Class<?> c) { - if (dex == c.getDex()) { - return c.getDexTypeIndex(); - } - if (dex == null) { - return -1; - } - int typeIndex = dex.findTypeIndex(InternalNames.getInternalName(c)); - if (typeIndex < 0) { - typeIndex = -1; - } - return typeIndex; - } - - private static EncodedValueReader getAnnotationReader( Dex dex, AnnotatedElement element, String annotationName, int expectedFieldCount) { int annotationSetOffset = getAnnotationSetOffset(element); diff --git a/luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java b/luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java index ef22576..5919a19 100644 --- a/luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java +++ b/luni/src/main/java/libcore/reflect/GenericArrayTypeImpl.java @@ -18,6 +18,7 @@ package libcore.reflect; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Type; +import java.util.Objects; public final class GenericArrayTypeImpl implements GenericArrayType { private final Type componentType; @@ -34,6 +35,20 @@ public final class GenericArrayTypeImpl implements GenericArrayType { } } + @Override + public boolean equals(Object o) { + if (!(o instanceof GenericArrayType)) { + return false; + } + GenericArrayType that = (GenericArrayType) o; + return Objects.equals(getGenericComponentType(), that.getGenericComponentType()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getGenericComponentType()); + } + public String toString() { return componentType.toString() + "[]"; } diff --git a/luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java b/luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java index 99dfe8b..2cd5ac3 100644 --- a/luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java +++ b/luni/src/main/java/libcore/reflect/ParameterizedTypeImpl.java @@ -18,17 +18,22 @@ package libcore.reflect; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Objects; public final class ParameterizedTypeImpl implements ParameterizedType { private final ListOfTypes args; private final ParameterizedTypeImpl ownerType0; // Potentially unresolved. - private Type ownerTypeRes; - private Class rawType; // Already resolved. + private Type ownerTypeRes; // Potentially unresolved. + private Class rawType; // Potentially unresolved. private final String rawTypeName; - private ClassLoader loader; + private final ClassLoader loader; public ParameterizedTypeImpl(ParameterizedTypeImpl ownerType, String rawTypeName, ListOfTypes args, ClassLoader loader) { + if (args == null) { + throw new NullPointerException(); + } this.ownerType0 = ownerType; this.rawTypeName = rawTypeName; this.args = args; @@ -37,7 +42,6 @@ public final class ParameterizedTypeImpl implements ParameterizedType { public Type[] getActualTypeArguments() { - // ASSUMPTION: args is never null!!! return args.getResolvedTypes().clone(); } @@ -76,6 +80,23 @@ public final class ParameterizedTypeImpl implements ParameterizedType { } @Override + public boolean equals(Object o) { + if (!(o instanceof ParameterizedType)) { + return false; + } + ParameterizedType that = (ParameterizedType) o; + return Objects.equals(getRawType(), that.getRawType()) && + Objects.equals(getOwnerType(), that.getOwnerType()) && + Arrays.equals(args.getResolvedTypes(), that.getActualTypeArguments()); + } + + @Override + public int hashCode() { + return 31 * (31 * Objects.hashCode(getRawType()) + Objects.hashCode(getOwnerType())) + + Arrays.hashCode(args.getResolvedTypes()); + } + + @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(rawTypeName); diff --git a/luni/src/main/java/libcore/util/EmptyArray.java b/luni/src/main/java/libcore/util/EmptyArray.java index 1713bfc..eac06f2 100644 --- a/luni/src/main/java/libcore/util/EmptyArray.java +++ b/luni/src/main/java/libcore/util/EmptyArray.java @@ -23,7 +23,9 @@ public final class EmptyArray { public static final byte[] BYTE = new byte[0]; public static final char[] CHAR = new char[0]; public static final double[] DOUBLE = new double[0]; + public static final float[] FLOAT = new float[0]; public static final int[] INT = new int[0]; + public static final long[] LONG = new long[0]; public static final Class<?>[] CLASS = new Class[0]; public static final Object[] OBJECT = new Object[0]; diff --git a/luni/src/main/java/libcore/util/ZoneInfo.java b/luni/src/main/java/libcore/util/ZoneInfo.java index 54ee667..4d58d93 100644 --- a/luni/src/main/java/libcore/util/ZoneInfo.java +++ b/luni/src/main/java/libcore/util/ZoneInfo.java @@ -13,11 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +/* + * Elements of the WallTime class are a port of Bionic's localtime.c to Java. That code had the + * following header: + * + * This file is in the public domain, so clarified as of + * 1996-06-05 by Arthur David Olson. + */ package libcore.util; import java.util.Arrays; +import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.TimeZone; import libcore.io.BufferIterator; @@ -51,7 +59,7 @@ public final class ZoneInfo extends TimeZone { private final byte[] mTypes; private final byte[] mIsDsts; - public static TimeZone makeTimeZone(String id, BufferIterator it) { + public static ZoneInfo makeTimeZone(String id, BufferIterator it) { // Variable names beginning tzh_ correspond to those in "tzfile.h". // Check tzh_magic. @@ -148,22 +156,21 @@ public final class ZoneInfo extends TimeZone { mOffsets[i] -= mRawOffset; } - // Is this zone still observing DST? + // Is this zone observing DST currently or in the future? // We don't care if they've historically used it: most places have at least once. - // We want to know whether the last "schedule info" (the unix times in the mTransitions - // array) is in the future. If it is, DST is still relevant. // See http://code.google.com/p/android/issues/detail?id=877. // This test means that for somewhere like Morocco, which tried DST in 2009 but has // 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; - long currentUnixTime = System.currentTimeMillis() / 1000; - if (mTransitions.length > 0) { - // (We're really dealing with uint32_t values, so long is most convenient in Java.) - long latestScheduleTime = ((long) mTransitions[mTransitions.length - 1]) & 0xffffffff; - if (currentUnixTime < latestScheduleTime) { + int currentUnixTimeSeconds = (int) (System.currentTimeMillis() / 1000); + int i = mTransitions.length - 1; + while (i >= 0 && mTransitions[i] >= currentUnixTimeSeconds) { + if (mIsDsts[mTypes[i]] > 0) { usesDst = true; + break; } + i--; } mUseDst = usesDst; @@ -301,4 +308,670 @@ public final class ZoneInfo extends TimeZone { ",transitions=" + mTransitions.length + "]"; } + + @Override + public Object clone() { + // Overridden for documentation. The default clone() behavior is exactly what we want. + // Though mutable, the arrays of offset data are treated as immutable. Only ID and + // mRawOffset are mutable in this class, and those are an immutable object and a primitive + // respectively. + return super.clone(); + } + + /** + * A class that represents a "wall time". This class is modeled on the C tm struct and + * is used to support android.text.format.Time behavior. Unlike the tm struct the year is + * represented as the full year, not the years since 1900. + * + * <p>This class contains a rewrite of various native functions that android.text.format.Time + * once relied on such as mktime_tz and localtime_tz. This replacement does not support leap + * seconds but does try to preserve behavior around ambiguous date/times found in the BSD + * version of mktime that was previously used. + * + * <p>The original native code used a 32-bit value for time_t on 32-bit Android, which + * was the only variant of Android available at the time. To preserve old behavior this code + * deliberately uses {@code int} rather than {@code long} for most things and performs + * calculations in seconds. This creates deliberate truncation issues for date / times before + * 1901 and after 2038. This is intentional but might be fixed in future if all the knock-ons + * can be resolved: Application code may have come to rely on the range so previously values + * like zero for year could indicate an invalid date but if we move to long the year zero would + * be valid. + * + * <p>All offsets are considered to be safe for addition / subtraction / multiplication without + * worrying about overflow. All absolute time arithmetic is checked for overflow / underflow. + */ + public static class WallTime { + + // We use a GregorianCalendar (set to UTC) to handle all the date/time normalization logic + // and to convert from a broken-down date/time to a millis value. + // Unfortunately, it cannot represent an initial state with a zero day and would + // automatically normalize it, so we must copy values into and out of it as needed. + private final GregorianCalendar calendar; + + private int year; + private int month; + private int monthDay; + private int hour; + private int minute; + private int second; + private int weekDay; + private int yearDay; + private int isDst; + private int gmtOffsetSeconds; + + public WallTime() { + this.calendar = createGregorianCalendar(); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + // LayoutLib replaces this method via bytecode manipulation, since the + // minimum-cost constructor is not available on host machines. + private static GregorianCalendar createGregorianCalendar() { + return new GregorianCalendar(false); + } + + /** + * Sets the wall time to a point in time using the time zone information provided. This + * is a replacement for the old native localtime_tz() function. + * + * <p>When going from an instant to a wall time it is always unambiguous because there + * is only one offset rule acting at any given instant. We do not consider leap seconds. + */ + public void localtime(int timeSeconds, ZoneInfo zoneInfo) { + try { + int offsetSeconds = zoneInfo.mRawOffset / 1000; + + // Find out the timezone DST state and adjustment. + byte isDst; + if (zoneInfo.mTransitions.length == 0) { + isDst = 0; + } else { + // transitionIndex can be in the range -1..zoneInfo.mTransitions.length - 1 + int transitionIndex = findTransitionIndex(zoneInfo, timeSeconds); + if (transitionIndex < 0) { + // -1 means timeSeconds is "before the first recorded transition". The first + // recorded transition is treated as a transition from non-DST and the raw + // offset. + isDst = 0; + } else { + byte transitionType = zoneInfo.mTypes[transitionIndex]; + offsetSeconds += zoneInfo.mOffsets[transitionType]; + isDst = zoneInfo.mIsDsts[transitionType]; + } + } + + // Perform arithmetic that might underflow before setting fields. + int wallTimeSeconds = checkedAdd(timeSeconds, offsetSeconds); + + // Set fields. + calendar.setTimeInMillis(wallTimeSeconds * 1000L); + copyFieldsFromCalendar(); + this.isDst = isDst; + this.gmtOffsetSeconds = offsetSeconds; + } catch (CheckedArithmeticException e) { + // Just stop, leaving fields untouched. + } + } + + /** + * Returns the time in seconds since beginning of the Unix epoch for the wall time using the + * time zone information provided. This is a replacement for an old native mktime_tz() C + * function. + * + * <p>When going from a wall time to an instant the answer can be ambiguous. A wall + * time can map to zero, one or two instants given sane date/time transitions. Sane + * in this case means that transitions occur less frequently than the offset + * differences between them (which could cause all sorts of craziness like the + * skipping out of transitions). + * + * <p>For example, this is not fully supported: + * <ul> + * <li>t1 { time = 1, offset = 0 } + * <li>t2 { time = 2, offset = -1 } + * <li>t3 { time = 3, offset = -2 } + * </ul> + * A wall time in this case might map to t1, t2 or t3. + * + * <p>We do not handle leap seconds. + * <p>We assume that no timezone offset transition has an absolute offset > 24 hours. + * <p>We do not assume that adjacent transitions modify the DST state; adjustments can + * occur for other reasons such as when a zone changes its raw offset. + */ + public int mktime(ZoneInfo zoneInfo) { + // Normalize isDst to -1, 0 or 1 to simplify isDst equality checks below. + this.isDst = this.isDst > 0 ? this.isDst = 1 : this.isDst < 0 ? this.isDst = -1 : 0; + + copyFieldsToCalendar(); + final long longWallTimeSeconds = calendar.getTimeInMillis() / 1000; + if (Integer.MIN_VALUE > longWallTimeSeconds + || longWallTimeSeconds > Integer.MAX_VALUE) { + // For compatibility with the old native 32-bit implementation we must treat + // this as an error. Note: -1 could be confused with a real time. + return -1; + } + + try { + final int wallTimeSeconds = (int) longWallTimeSeconds; + final int rawOffsetSeconds = zoneInfo.mRawOffset / 1000; + final int rawTimeSeconds = checkedSubtract(wallTimeSeconds, rawOffsetSeconds); + + if (zoneInfo.mTransitions.length == 0) { + // There is no transition information. There is just a raw offset for all time. + if (this.isDst > 0) { + // Caller has asserted DST, but there is no DST information available. + return -1; + } + copyFieldsFromCalendar(); + this.isDst = 0; + this.gmtOffsetSeconds = rawOffsetSeconds; + return rawTimeSeconds; + } + + // We cannot know for sure what instant the wall time will map to. Unfortunately, in + // order to know for sure we need the timezone information, but to get the timezone + // information we need an instant. To resolve this we use the raw offset to find an + // OffsetInterval; this will get us the OffsetInterval we need or very close. + + // The initialTransition can be between -1 and (zoneInfo.mTransitions - 1). -1 + // indicates the rawTime is before the first transition and is handled gracefully by + // createOffsetInterval(). + final int initialTransitionIndex = findTransitionIndex(zoneInfo, rawTimeSeconds); + + if (isDst < 0) { + // This is treated as a special case to get it out of the way: + // When a caller has set isDst == -1 it means we can return the first match for + // the wall time we find. If the caller has specified a wall time that cannot + // exist this always returns -1. + + Integer result = doWallTimeSearch(zoneInfo, initialTransitionIndex, + wallTimeSeconds, true /* mustMatchDst */); + return result == null ? -1 : result; + } + + // If the wall time asserts a DST (isDst == 0 or 1) the search is performed twice: + // 1) The first attempts to find a DST offset that matches isDst exactly. + // 2) If it fails, isDst is assumed to be incorrect and adjustments are made to see + // if a valid wall time can be created. The result can be somewhat arbitrary. + + Integer result = doWallTimeSearch(zoneInfo, initialTransitionIndex, wallTimeSeconds, + true /* mustMatchDst */); + if (result == null) { + result = doWallTimeSearch(zoneInfo, initialTransitionIndex, wallTimeSeconds, + false /* mustMatchDst */); + } + if (result == null) { + result = -1; + } + return result; + } catch (CheckedArithmeticException e) { + return -1; + } + } + + /** + * Attempt to apply DST adjustments to {@code oldWallTimeSeconds} to create a wall time in + * {@code targetInterval}. + * + * <p>This is used when a caller has made an assertion about standard time / DST that cannot + * be matched to any offset interval that exists. We must therefore assume that the isDst + * assertion is incorrect and the invalid wall time is the result of some modification the + * caller made to a valid wall time that pushed them outside of the offset interval they + * were in. We must correct for any DST change that should have been applied when they did + * so. + * + * <p>Unfortunately, we have no information about what adjustment they made and so cannot + * know which offset interval they were previously in. For example, they may have added a + * second or a year to a valid time to arrive at what they have. + * + * <p>We try all offset types that are not the same as the isDst the caller asserted. For + * each possible offset we work out the offset difference between that and + * {@code targetInterval}, apply it, and see if we are still in {@code targetInterval}. If + * we are, then we have found an adjustment. + */ + private Integer tryOffsetAdjustments(ZoneInfo zoneInfo, int oldWallTimeSeconds, + OffsetInterval targetInterval, int transitionIndex, int isDstToFind) + throws CheckedArithmeticException { + + int[] offsetsToTry = getOffsetsOfType(zoneInfo, transitionIndex, isDstToFind); + for (int j = 0; j < offsetsToTry.length; j++) { + int rawOffsetSeconds = zoneInfo.mRawOffset / 1000; + int jOffsetSeconds = rawOffsetSeconds + offsetsToTry[j]; + int targetIntervalOffsetSeconds = targetInterval.getTotalOffsetSeconds(); + int adjustmentSeconds = targetIntervalOffsetSeconds - jOffsetSeconds; + int adjustedWallTimeSeconds = checkedAdd(oldWallTimeSeconds, adjustmentSeconds); + if (targetInterval.containsWallTime(adjustedWallTimeSeconds)) { + // Perform any arithmetic that might overflow. + int returnValue = checkedSubtract(adjustedWallTimeSeconds, + targetIntervalOffsetSeconds); + + // Modify field state and return the result. + calendar.setTimeInMillis(adjustedWallTimeSeconds * 1000L); + copyFieldsFromCalendar(); + this.isDst = targetInterval.getIsDst(); + this.gmtOffsetSeconds = targetIntervalOffsetSeconds; + return returnValue; + } + } + return null; + } + + /** + * Return an array of offsets that have the requested {@code isDst} value. + * The {@code startIndex} is used as a starting point so transitions nearest + * to that index are returned first. + */ + private static int[] getOffsetsOfType(ZoneInfo zoneInfo, int startIndex, int isDst) { + // +1 to account for the synthetic transition we invent before the first recorded one. + int[] offsets = new int[zoneInfo.mOffsets.length + 1]; + boolean[] seen = new boolean[zoneInfo.mOffsets.length]; + int numFound = 0; + + int delta = 0; + boolean clampTop = false; + boolean clampBottom = false; + do { + // delta = { 1, -1, 2, -2, 3, -3...} + delta *= -1; + if (delta >= 0) { + delta++; + } + + int transitionIndex = startIndex + delta; + if (delta < 0 && transitionIndex < -1) { + clampBottom = true; + continue; + } else if (delta > 0 && transitionIndex >= zoneInfo.mTypes.length) { + clampTop = true; + continue; + } + + if (transitionIndex == -1) { + if (isDst == 0) { + // Synthesize a non-DST transition before the first transition we have + // data for. + offsets[numFound++] = 0; // offset of 0 from raw offset + } + continue; + } + byte type = zoneInfo.mTypes[transitionIndex]; + if (!seen[type]) { + if (zoneInfo.mIsDsts[type] == isDst) { + offsets[numFound++] = zoneInfo.mOffsets[type]; + } + seen[type] = true; + } + } while (!(clampTop && clampBottom)); + + int[] toReturn = new int[numFound]; + System.arraycopy(offsets, 0, toReturn, 0, numFound); + return toReturn; + } + + /** + * Find a time <em>in seconds</em> the same or close to {@code wallTimeSeconds} that + * satisfies {@code mustMatchDst}. The search begins around the timezone offset transition + * with {@code initialTransitionIndex}. + * + * <p>If {@code mustMatchDst} is {@code true} the method can only return times that + * use timezone offsets that satisfy the {@code this.isDst} requirements. + * If {@code this.isDst == -1} it means that any offset can be used. + * + * <p>If {@code mustMatchDst} is {@code false} any offset that covers the + * currently set time is acceptable. That is: if {@code this.isDst} == -1, any offset + * transition can be used, if it is 0 or 1 the offset used must match {@code this.isDst}. + * + * <p>Note: This method both uses and can modify field state. It returns the matching time + * in seconds if a match has been found and modifies fields, or it returns {@code null} and + * leaves the field state unmodified. + */ + private Integer doWallTimeSearch(ZoneInfo zoneInfo, int initialTransitionIndex, + int wallTimeSeconds, boolean mustMatchDst) throws CheckedArithmeticException { + + // The loop below starts at the initialTransitionIndex and radiates out from that point + // up to 24 hours in either direction by applying transitionIndexDelta to inspect + // adjacent transitions (0, -1, +1, -2, +2). 24 hours is used because we assume that no + // total offset from UTC is ever > 24 hours. clampTop and clampBottom are used to + // indicate whether the search has either searched > 24 hours or exhausted the + // transition data in that direction. The search stops when a match is found or if + // clampTop and clampBottom are both true. + // The match logic employed is determined by the mustMatchDst parameter. + final int MAX_SEARCH_SECONDS = 24 * 60 * 60; + boolean clampTop = false, clampBottom = false; + int loop = 0; + do { + // transitionIndexDelta = { 0, -1, 1, -2, 2,..} + int transitionIndexDelta = (loop + 1) / 2; + if (loop % 2 == 1) { + transitionIndexDelta *= -1; + } + loop++; + + // Only do any work in this iteration if we need to. + if (transitionIndexDelta > 0 && clampTop + || transitionIndexDelta < 0 && clampBottom) { + continue; + } + + // Obtain the OffsetInterval to use. + int currentTransitionIndex = initialTransitionIndex + transitionIndexDelta; + OffsetInterval offsetInterval = + OffsetInterval.create(zoneInfo, currentTransitionIndex); + if (offsetInterval == null) { + // No transition exists with the index we tried: Stop searching in the + // current direction. + clampTop |= (transitionIndexDelta > 0); + clampBottom |= (transitionIndexDelta < 0); + continue; + } + + // Match the wallTimeSeconds against the OffsetInterval. + if (mustMatchDst) { + // Work out if the interval contains the wall time the caller specified and + // matches their isDst value. + if (offsetInterval.containsWallTime(wallTimeSeconds)) { + if (this.isDst == -1 || offsetInterval.getIsDst() == this.isDst) { + // This always returns the first OffsetInterval it finds that matches + // the wall time and isDst requirements. If this.isDst == -1 this means + // the result might be a DST or a non-DST answer for wall times that can + // exist in two OffsetIntervals. + int totalOffsetSeconds = offsetInterval.getTotalOffsetSeconds(); + int returnValue = checkedSubtract(wallTimeSeconds, + totalOffsetSeconds); + + copyFieldsFromCalendar(); + this.isDst = offsetInterval.getIsDst(); + this.gmtOffsetSeconds = totalOffsetSeconds; + return returnValue; + } + } + } else { + // To retain similar behavior to the old native implementation: if the caller is + // asserting the same isDst value as the OffsetInterval we are looking at we do + // not try to find an adjustment from another OffsetInterval of the same isDst + // type. If you remove this you get different results in situations like a + // DST -> DST transition or STD -> STD transition that results in an interval of + // "skipped" wall time. For example: if 01:30 (DST) is invalid and between two + // DST intervals, and the caller has passed isDst == 1, this results in a -1 + // being returned. + if (isDst != offsetInterval.getIsDst()) { + final int isDstToFind = isDst; + Integer returnValue = tryOffsetAdjustments(zoneInfo, wallTimeSeconds, + offsetInterval, currentTransitionIndex, isDstToFind); + if (returnValue != null) { + return returnValue; + } + } + } + + // See if we can avoid another loop in the current direction. + if (transitionIndexDelta > 0) { + // If we are searching forward and the OffsetInterval we have ends + // > MAX_SEARCH_SECONDS after the wall time, we don't need to look any further + // forward. + boolean endSearch = offsetInterval.getEndWallTimeSeconds() - wallTimeSeconds + > MAX_SEARCH_SECONDS; + if (endSearch) { + clampTop = true; + } + } else if (transitionIndexDelta < 0) { + boolean endSearch = wallTimeSeconds - offsetInterval.getStartWallTimeSeconds() + >= MAX_SEARCH_SECONDS; + if (endSearch) { + // If we are searching backward and the OffsetInterval starts + // > MAX_SEARCH_SECONDS before the wall time, we don't need to look any + // further backwards. + clampBottom = true; + } + } + } while (!(clampTop && clampBottom)); + return null; + } + + public void setYear(int year) { + this.year = year; + } + + public void setMonth(int month) { + this.month = month; + } + + public void setMonthDay(int monthDay) { + this.monthDay = monthDay; + } + + public void setHour(int hour) { + this.hour = hour; + } + + public void setMinute(int minute) { + this.minute = minute; + } + + public void setSecond(int second) { + this.second = second; + } + + public void setWeekDay(int weekDay) { + this.weekDay = weekDay; + } + + public void setYearDay(int yearDay) { + this.yearDay = yearDay; + } + + public void setIsDst(int isDst) { + this.isDst = isDst; + } + + public void setGmtOffset(int gmtoff) { + this.gmtOffsetSeconds = gmtoff; + } + + public int getYear() { + return year; + } + + public int getMonth() { + return month; + } + + public int getMonthDay() { + return monthDay; + } + + public int getHour() { + return hour; + } + + public int getMinute() { + return minute; + } + + public int getSecond() { + return second; + } + + public int getWeekDay() { + return weekDay; + } + + public int getYearDay() { + return yearDay; + } + + public int getGmtOffset() { + return gmtOffsetSeconds; + } + + public int getIsDst() { + return isDst; + } + + private void copyFieldsToCalendar() { + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month); + calendar.set(Calendar.DAY_OF_MONTH, monthDay); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + } + + private void copyFieldsFromCalendar() { + year = calendar.get(Calendar.YEAR); + month = calendar.get(Calendar.MONTH); + monthDay = calendar.get(Calendar.DAY_OF_MONTH); + hour = calendar.get(Calendar.HOUR_OF_DAY); + minute = calendar.get(Calendar.MINUTE); + second = calendar.get(Calendar.SECOND); + + // Calendar uses Sunday == 1. Android Time uses Sunday = 0. + weekDay = calendar.get(Calendar.DAY_OF_WEEK) - 1; + // Calendar enumerates from 1, Android Time enumerates from 0. + yearDay = calendar.get(Calendar.DAY_OF_YEAR) - 1; + } + + /** + * Find the transition in the {@code timezone} in effect at {@code timeSeconds}. + * + * <p>Returns an index in the range -1..timeZone.mTransitions.length - 1. -1 is used to + * indicate the time is before the first transition. Other values are an index into + * timeZone.mTransitions. + */ + private static int findTransitionIndex(ZoneInfo timeZone, int timeSeconds) { + int matchingRawTransition = Arrays.binarySearch(timeZone.mTransitions, timeSeconds); + if (matchingRawTransition < 0) { + matchingRawTransition = ~matchingRawTransition - 1; + } + return matchingRawTransition; + } + } + + /** + * A wall-time representation of a timezone offset interval. + * + * <p>Wall-time means "as it would appear locally in the timezone in which it applies". + * For example in 2007: + * PST was a -8:00 offset that ran until Mar 11, 2:00 AM. + * PDT was a -7:00 offset and ran from Mar 11, 3:00 AM to Nov 4, 2:00 AM. + * PST was a -8:00 offset and ran from Nov 4, 1:00 AM. + * Crucially this means that there was a "gap" after PST when PDT started, and an overlap when + * PDT ended and PST began. + * + * <p>For convenience all wall-time values are represented as the number of seconds since the + * beginning of the Unix epoch <em>in UTC</em>. To convert from a wall-time to the actual time + * in the offset it is necessary to <em>subtract</em> the {@code totalOffsetSeconds}. + * For example: If the offset in PST is -07:00 hours, then: + * timeInPstSeconds = wallTimeUtcSeconds - offsetSeconds + * i.e. 13:00 UTC - (-07:00) = 20:00 UTC = 13:00 PST + */ + static class OffsetInterval { + + private final int startWallTimeSeconds; + private final int endWallTimeSeconds; + private final int isDst; + private final int totalOffsetSeconds; + + /** + * Creates an {@link OffsetInterval}. + * + * <p>If {@code transitionIndex} is -1, the transition is synthesized to be a non-DST offset + * that runs from the beginning of time until the first transition in {@code timeZone} and + * has an offset of {@code timezone.mRawOffset}. If {@code transitionIndex} is the last + * transition that transition is considered to run until the end of representable time. + * Otherwise, the information is extracted from {@code timeZone.mTransitions}, + * {@code timeZone.mOffsets} an {@code timeZone.mIsDsts}. + */ + public static OffsetInterval create(ZoneInfo timeZone, int transitionIndex) + throws CheckedArithmeticException { + + if (transitionIndex < -1 || transitionIndex >= timeZone.mTransitions.length) { + return null; + } + + int rawOffsetSeconds = timeZone.mRawOffset / 1000; + if (transitionIndex == -1) { + int endWallTimeSeconds = checkedAdd(timeZone.mTransitions[0], rawOffsetSeconds); + return new OffsetInterval(Integer.MIN_VALUE, endWallTimeSeconds, 0 /* isDst */, + rawOffsetSeconds); + } + + byte type = timeZone.mTypes[transitionIndex]; + int totalOffsetSeconds = timeZone.mOffsets[type] + rawOffsetSeconds; + int endWallTimeSeconds; + if (transitionIndex == timeZone.mTransitions.length - 1) { + // If this is the last transition, make up the end time. + endWallTimeSeconds = Integer.MAX_VALUE; + } else { + endWallTimeSeconds = checkedAdd(timeZone.mTransitions[transitionIndex + 1], + totalOffsetSeconds); + } + int isDst = timeZone.mIsDsts[type]; + int startWallTimeSeconds = + checkedAdd(timeZone.mTransitions[transitionIndex], totalOffsetSeconds); + return new OffsetInterval( + startWallTimeSeconds, endWallTimeSeconds, isDst, totalOffsetSeconds); + } + + private OffsetInterval(int startWallTimeSeconds, int endWallTimeSeconds, int isDst, + int totalOffsetSeconds) { + this.startWallTimeSeconds = startWallTimeSeconds; + this.endWallTimeSeconds = endWallTimeSeconds; + this.isDst = isDst; + this.totalOffsetSeconds = totalOffsetSeconds; + } + + public boolean containsWallTime(long wallTimeSeconds) { + return wallTimeSeconds >= startWallTimeSeconds && wallTimeSeconds < endWallTimeSeconds; + } + + public int getIsDst() { + return isDst; + } + + public int getTotalOffsetSeconds() { + return totalOffsetSeconds; + } + + public long getEndWallTimeSeconds() { + return endWallTimeSeconds; + } + + public long getStartWallTimeSeconds() { + return startWallTimeSeconds; + } + } + + /** + * An exception used to indicate an arithmetic overflow or underflow. + */ + private static class CheckedArithmeticException extends Exception { + } + + /** + * Calculate (a + b). + * + * @throws CheckedArithmeticException if overflow or underflow occurs + */ + private static int checkedAdd(int a, int b) throws CheckedArithmeticException { + // Adapted from Guava IntMath.checkedAdd(); + long result = (long) a + b; + if (result != (int) result) { + throw new CheckedArithmeticException(); + } + return (int) result; + } + + /** + * Calculate (a - b). + * + * @throws CheckedArithmeticException if overflow or underflow occurs + */ + private static int checkedSubtract(int a, int b) throws CheckedArithmeticException { + // Adapted from Guava IntMath.checkedSubtract(); + long result = (long) a - b; + if (result != (int) result) { + throw new CheckedArithmeticException(); + } + return (int) result; + } } diff --git a/luni/src/main/java/libcore/util/ZoneInfoDB.java b/luni/src/main/java/libcore/util/ZoneInfoDB.java index 10e3900..a9d06a4 100644 --- a/luni/src/main/java/libcore/util/ZoneInfoDB.java +++ b/luni/src/main/java/libcore/util/ZoneInfoDB.java @@ -16,6 +16,7 @@ package libcore.util; +import android.system.ErrnoException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; @@ -27,7 +28,6 @@ import java.util.Arrays; import java.util.List; import java.util.TimeZone; import libcore.io.BufferIterator; -import libcore.io.ErrnoException; import libcore.io.IoUtils; import libcore.io.MemoryMappedFile; @@ -62,11 +62,33 @@ public final class ZoneInfoDB { /** * The 'ids' array contains time zone ids sorted alphabetically, for binary searching. * The other two arrays are in the same order. 'byteOffsets' gives the byte offset - * of each time zone, and 'rawUtcOffsets' gives the time zone's raw UTC offset. + * of each time zone, and 'rawUtcOffsetsCache' gives the time zone's raw UTC offset. */ private String[] ids; private int[] byteOffsets; - private int[] rawUtcOffsets; + private int[] rawUtcOffsetsCache; // Access this via getRawUtcOffsets instead. + + /** + * ZoneInfo objects are worth caching because they are expensive to create. + * See http://b/8270865 for context. + */ + private final static int CACHE_SIZE = 1; + private final BasicLruCache<String, ZoneInfo> cache = + new BasicLruCache<String, ZoneInfo>(CACHE_SIZE) { + @Override + protected ZoneInfo create(String id) { + // Work out where in the big data file this time zone is. + int index = Arrays.binarySearch(ids, id); + if (index < 0) { + return null; + } + + BufferIterator it = mappedFile.bigEndianIterator(); + it.skip(byteOffsets[index]); + + return ZoneInfo.makeTimeZone(id, it); + } + }; public TzData(String... paths) { for (String path : paths) { @@ -82,7 +104,7 @@ public final class ZoneInfoDB { version = "missing"; zoneTab = "# Emergency fallback data.\n"; ids = new String[] { "GMT" }; - byteOffsets = rawUtcOffsets = new int[1]; + byteOffsets = rawUtcOffsetsCache = new int[1]; } private boolean loadData(String path) { @@ -149,7 +171,6 @@ public final class ZoneInfoDB { int idOffset = 0; byteOffsets = new int[entryCount]; - rawUtcOffsets = new int[entryCount]; for (int i = 0; i < entryCount; i++) { it.readByteArray(idBytes, 0, idBytes.length); @@ -161,7 +182,7 @@ public final class ZoneInfoDB { if (length < 44) { throw new AssertionError("length in index file < sizeof(tzhead)"); } - rawUtcOffsets[i] = it.readInt(); + it.skip(4); // Skip the unused 4 bytes that used to be the raw offset. // Don't include null chars in the String int len = idBytes.length; @@ -188,16 +209,33 @@ public final class ZoneInfoDB { return ids.clone(); } - public String[] getAvailableIDs(int rawOffset) { + public String[] getAvailableIDs(int rawUtcOffset) { List<String> matches = new ArrayList<String>(); - for (int i = 0, end = rawUtcOffsets.length; i < end; ++i) { - if (rawUtcOffsets[i] == rawOffset) { + int[] rawUtcOffsets = getRawUtcOffsets(); + for (int i = 0; i < rawUtcOffsets.length; ++i) { + if (rawUtcOffsets[i] == rawUtcOffset) { matches.add(ids[i]); } } return matches.toArray(new String[matches.size()]); } + private synchronized int[] getRawUtcOffsets() { + if (rawUtcOffsetsCache != null) { + return rawUtcOffsetsCache; + } + rawUtcOffsetsCache = new int[ids.length]; + for (int i = 0; i < ids.length; ++i) { + // This creates a TimeZone, which is quite expensive. Hence the cache. + // Note that icu4c does the same (without the cache), so if you're + // switching this code over to icu4j you should check its performance. + // Telephony shouldn't care, but someone converting a bunch of calendar + // events might. + rawUtcOffsetsCache[i] = cache.get(ids[i]).getRawOffset(); + } + return rawUtcOffsetsCache; + } + public String getVersion() { return version; } @@ -206,17 +244,10 @@ public final class ZoneInfoDB { return zoneTab; } - public TimeZone makeTimeZone(String id) throws IOException { - // Work out where in the big data file this time zone is. - int index = Arrays.binarySearch(ids, id); - if (index < 0) { - return null; - } - - BufferIterator it = mappedFile.bigEndianIterator(); - it.skip(byteOffsets[index]); - - return ZoneInfo.makeTimeZone(id, it); + public ZoneInfo makeTimeZone(String id) throws IOException { + ZoneInfo zoneInfo = cache.get(id); + // The object from the cache is cloned because TimeZone / ZoneInfo are mutable. + return zoneInfo == null ? null : (ZoneInfo) zoneInfo.clone(); } } diff --git a/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java b/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java index f1dd43c..855a8c7 100644 --- a/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java +++ b/luni/src/main/java/org/apache/harmony/security/fortress/Engine.java @@ -24,15 +24,15 @@ package org.apache.harmony.security.fortress; import java.security.NoSuchAlgorithmException; import java.security.Provider; +import java.util.ArrayList; import java.util.Locale; - /** * This class implements common functionality for Provider supplied * classes. The usage pattern is to allocate static Engine instance * per service type and synchronize on that instance during calls to - * {@code getInstance} and retreival of the selected {@code Provider} - * and Service Provider Interface (SPI) results. Retreiving the + * {@code getInstance} and retrieval of the selected {@code Provider} + * and Service Provider Interface (SPI) results. Retrieving the * results with {@code getProvider} and {@code getSpi} sets the * internal {@code Engine} values to null to prevent memory leaks. * @@ -69,7 +69,7 @@ import java.util.Locale; * * }</pre> */ -public class Engine { +public final class Engine { /** * Access to package visible api in java.security @@ -95,14 +95,14 @@ public class Engine { /** used to test for cache validity */ private final int cacheVersion; /** cached result */ - private final Provider.Service service; + private final ArrayList<Provider.Service> services; private ServiceCacheEntry(String algorithm, int cacheVersion, - Provider.Service service) { + ArrayList<Provider.Service> services) { this.algorithm = algorithm; this.cacheVersion = cacheVersion; - this.service = service; + this.services = services; } } @@ -118,47 +118,60 @@ public class Engine { /** * Creates a Engine object * - * @param service + * @param serviceName */ - public Engine(String service) { - this.serviceName = service; + public Engine(String serviceName) { + this.serviceName = serviceName; } /** * Finds the appropriate service implementation and returns an - * {@code SpiAndProvider} instance containing a reference to SPI - * and its {@code Provider} + * {@code SpiAndProvider} instance containing a reference to the first + * matching SPI and its {@code Provider} */ public SpiAndProvider getInstance(String algorithm, Object param) throws NoSuchAlgorithmException { if (algorithm == null) { throw new NoSuchAlgorithmException("Null algorithm name"); } + ArrayList<Provider.Service> services = getServices(algorithm); + if (services == null) { + throw notFound(this.serviceName, algorithm); + } + return new SpiAndProvider(services.get(0).newInstance(param), services.get(0).getProvider()); + } + + /** + * Finds the appropriate service implementation and returns an + * {@code SpiAndProvider} instance containing a reference to SPI + * and its {@code Provider} + */ + public SpiAndProvider getInstance(Provider.Service service, String param) + throws NoSuchAlgorithmException { + return new SpiAndProvider(service.newInstance(param), service.getProvider()); + } + + /** + * Returns a list of all possible matches for a given algorithm. + */ + public ArrayList<Provider.Service> getServices(String algorithm) { int newCacheVersion = Services.getCacheVersion(); - Provider.Service service; ServiceCacheEntry cacheEntry = this.serviceCache; + final String algoUC = algorithm.toUpperCase(Locale.US); if (cacheEntry != null - && cacheEntry.algorithm.equalsIgnoreCase(algorithm) + && cacheEntry.algorithm.equalsIgnoreCase(algoUC) && newCacheVersion == cacheEntry.cacheVersion) { - service = cacheEntry.service; - } else { - if (Services.isEmpty()) { - throw notFound(serviceName, algorithm); - } - String name = this.serviceName + "." + algorithm.toUpperCase(Locale.US); - service = Services.getService(name); - if (service == null) { - throw notFound(serviceName, algorithm); - } - this.serviceCache = new ServiceCacheEntry(algorithm, newCacheVersion, service); + return cacheEntry.services; } - return new SpiAndProvider(service.newInstance(param), service.getProvider()); + String name = this.serviceName + "." + algoUC; + ArrayList<Provider.Service> services = Services.getServices(name); + this.serviceCache = new ServiceCacheEntry(algoUC, newCacheVersion, services); + return services; } /** - * Finds the appropriate service implementation and returns and - * instance of the class that implements corresponding Service - * Provider Interface. + * Finds the appropriate service implementation and returns and instance of + * the class that implements corresponding Service Provider Interface. */ public Object getInstance(String algorithm, Provider provider, Object param) throws NoSuchAlgorithmException { 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 4fe0d44..30f4839 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 @@ -21,9 +21,7 @@ import java.security.Provider; import java.security.Security; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Locale; -import java.util.Map; /** @@ -38,8 +36,8 @@ public class Services { * Set the initial size to 600 so we don't grow to 1024 by default because * initialization adds a few entries more than the growth threshold. */ - private static final Map<String, Provider.Service> services - = new HashMap<String, Provider.Service>(600); + private static final HashMap<String, ArrayList<Provider.Service>> services + = new HashMap<String, ArrayList<Provider.Service>>(600); /** * Save default SecureRandom service as well. @@ -62,12 +60,13 @@ public class Services { /** * Registered providers. */ - private static final List<Provider> providers = new ArrayList<Provider>(20); + private static final ArrayList<Provider> providers = new ArrayList<Provider>(20); /** * Hash for quick provider access by name. */ - private static final Map<String, Provider> providersNames = new HashMap<String, Provider>(20); + private static final HashMap<String, Provider> providersNames + = new HashMap<String, Provider>(20); static { String providerClassName = null; int i = 1; @@ -75,7 +74,7 @@ public class Services { while ((providerClassName = Security.getProperty("security.provider." + i++)) != null) { try { - Class providerClass = Class.forName(providerClassName.trim(), true, cl); + Class<?> providerClass = Class.forName(providerClassName.trim(), true, cl); Provider p = (Provider) providerClass.newInstance(); providers.add(p); providersNames.put(p.getName(), p); @@ -91,15 +90,8 @@ public class Services { /** * Returns a copy of the registered providers as an array. */ - public static synchronized Provider[] getProviders() { - return providers.toArray(new Provider[providers.size()]); - } - - /** - * Returns a copy of the registered providers as a list. - */ - public static synchronized List<Provider> getProvidersList() { - return new ArrayList<Provider>(providers); + public static synchronized ArrayList<Provider> getProviders() { + return providers; } /** @@ -145,20 +137,28 @@ public class Services { cachedSecureRandomService = service; } String key = type + "." + service.getAlgorithm().toUpperCase(Locale.US); - if (!services.containsKey(key)) { - services.put(key, service); - } + appendServiceLocked(key, service); for (String alias : Engine.door.getAliases(service)) { key = type + "." + alias.toUpperCase(Locale.US); - if (!services.containsKey(key)) { - services.put(key, service); - } + appendServiceLocked(key, service); } } } /** - * Returns true if services contain any provider information. + * Add or append the service to the key. + */ + private static void appendServiceLocked(String key, Provider.Service service) { + ArrayList<Provider.Service> serviceList = services.get(key); + if (serviceList == null) { + serviceList = new ArrayList<Provider.Service>(1); + services.put(key, serviceList); + } + serviceList.add(service); + } + + /** + * Returns true if services does not contain any provider information. */ public static synchronized boolean isEmpty() { return services.isEmpty(); @@ -174,7 +174,7 @@ public class Services { * caches should be validated against the result of * Service.getCacheVersion() before use. */ - public static synchronized Provider.Service getService(String key) { + public static synchronized ArrayList<Provider.Service> getServices(String key) { return services.get(key); } diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/Cache.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/Cache.java deleted file mode 100644 index a2c5b4c..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/Cache.java +++ /dev/null @@ -1,324 +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. - */ - -/** -* @author Alexander Y. Kleymenov -* @version $Revision$ -*/ - -package org.apache.harmony.security.provider.cert; - -import java.util.Arrays; - -/** - * The caching mechanism designed to speed up the process - * of Certificates/CRLs generation in the case of their repeated - * generation. - * - * It keeps correspondences between Objects (Certificates or CLRs) - * and arrays of bytes on the base of which the Objects have been generated, - * and provides the means to determine whether it contains the object built on - * the base of particular encoded form or not. If there are such - * objects they are returned from the cache, if not - newly generated - * objects can be saved in the cache.<br> - * - * The process of Certificate/CRL generation - * (implemented in <code>X509CertFactoryImpl</code>) is accompanied with - * prereading of the beginning of encoded form. This prefix is used to determine - * whether provided form is PEM encoding or not.<br> - * - * So the use of the prefix is the first point to (approximately) - * determine whether object to be generated is in the cache or not. - * - * The failure of the predetermination process tells us that there were not - * object generated from the encoded form with such prefix and we should - * generate (decode) the object. If predetermination is successful, - * we conduct the accurate search on the base of whole encoded form. <br> - * - * So to speed up the object generation process this caching mechanism provides - * the following functionality:<br> - * - * 1. With having of the beginning of the encoded form (prefix) - * it is possible to predetermine whether object has already been - * generated on the base of the encoding with the SIMILAR prefix or not. - * This process is not computationally expensive and takes a little time. - * But it prevents us from use of expensive full encoding - * search in the case of its failure.<br> - * - * 2. If predetermination ends with success, the whole encoding - * form should be provided to make the final answer: whether object has - * already been generated on the base of this PARTICULAR encoded form or not. - * If it is so - the cached object is returned from the cache, - * if not - new object should be generated and saved in the cache.<br> - * - * Note: The length of the prefixes of the encoded forms should not be - * less than correspondence (default value is 28). - */ -public class Cache { - - // Hash code consist of 6 bytes: AABB00 - // where: - // AA - 2 bytes for prefix hash - // value generated on the base of the prefix of encoding - // BB - 2 bytes for tail hash - // value generated on the base of the tail of encoding - // 00 - 2 reserved bytes equals to 0 - // - // Note, that it is possible for 2 different arrays to have - // the similar hash codes. - - // The masks to work with hash codes: - // the hash code without the reserved bytes - private static final long HASH_MASK = 0xFFFFFFFFFFFF0000L; - // the hash code of the prefix - private static final long PREFIX_HASH_MASK = 0xFFFFFFFF00000000L; - // the index value contained in reserved bytes - private static final int INDEX_MASK = 0x00FFFF; - - // size of the cache - private final int cache_size; - // the number of bytes which will be used for array hash generation. - private final int prefix_size; - - // The following 3 arrays contain the information about cached objects. - // This information includes: hash of the array, encoded form of the object, - // and the object itself. - // The hash-encoding-object correspondence is made by means of index - // in the particular array. I.e. for index N hash contained in hashes[N] - // corresponds to the encoding contained in encodings[N] which corresponds - // to the object cached at cache[N] - - // array containing the hash codes of encodings - private final long[] hashes; - // array containing the encodings of the cached objects - private final byte[][] encodings; - // array containing the cached objects - private final Object[] cache; - - // This array is used to speed up the process of the search in the cache. - // This is an ordered array of the hash codes from 'hashes' array (described - // above) with last 2 (reserved) bytes equals to the index of - // the hash in the 'hashes' array. I.e. hash code ABCD00 with index 10 in - // the hashes array will be represented in this array as ABCD0A (10==0x0A) - // So this array contains ordered <hash to index> correspondences. - // Note, that every item in this array is unique. - private final long[] hashes_idx; - - // the index of the last cached object - private int last_cached = 0; - // cache population indicator - private boolean cache_is_full = false; - - /** - * Creates the Cache object. - * @param pref_size specifies how many leading/trailing bytes of object's - * encoded form will be used for hash computation - * @param size capacity of the cache to be created. - */ - public Cache(int pref_size, int size) { - cache_size = size; - prefix_size = pref_size; - hashes = new long[cache_size]; - hashes_idx = new long[cache_size]; - encodings = new byte[cache_size][]; - cache = new Object[cache_size]; - } - - /** - * Creates the Cache object of size of 9. - * @param pref_size specifies how many leading/trailing bytes of object's - * encoded form will be used for hash computation - */ - public Cache(int pref_size) { - this(pref_size, 9); - } - - /** - * Creates the Cache object of size of 9. - */ - public Cache() { - this(28, 9); - } - - /** - * Returns the hash code for the array. This code is used to - * predetermine whether the object was built on the base of the - * similar encoding or not (by means of <code>contains(long)</code> method), - * to exactly determine whether object is contained in the cache or not, - * and to put the object in the cache. - * Note: parameter array should be of length not less than - * specified by <code>prefix_size</code> (default 28) - * @param arr the byte array containing at least prefix_size leading bytes - * of the encoding. - * @return hash code for specified encoding prefix - */ - public long getHash(byte[] arr) { - long hash = 0; - for (int i=1; i<prefix_size; i++) { - hash += (arr[i] & 0xFF); - } // it takes about 2 bytes for prefix_size == 28 - - // shift to the correct place - hash = hash << 32; - return hash; - } - - /** - * Checks if there are any object in the cache generated - * on the base of encoding with prefix corresponding - * to the specified hash code. - * @param prefix_hash the hash code for the prefix - * of the encoding (retrieved by method <code>getHash(byte[]))</code> - * @return false if there were not any object generated - * on the base of encoding with specified hash code, true - * otherwise. - */ - public boolean contains(long prefix_hash) { - if (prefix_hash == 0) { - return false; - } - int idx = -1*Arrays.binarySearch(hashes_idx, prefix_hash)-1; - if (idx == cache_size) { - return false; - } else { - return (hashes_idx[idx] & PREFIX_HASH_MASK) == prefix_hash; - } - } - - /** - * Returns the object built on the base on the specified encoded - * form if it is contained in the cache and null otherwise. - * This method is computationally expensive and should be called only if - * the method <code>contains(long)</code> for the hash code returned true. - * @param hash the hash code for the prefix of the encoding - * (retrieved by method <code>getHash(byte[])</code>) - * @param encoding encoded form of the required object. - * @return the object corresponding to specified encoding or null if - * there is no such correspondence. - */ - public Object get(long hash, byte[] encoding) { - hash |= getSuffHash(encoding); - if (hash == 0) { - return null; - } - int idx = -1*Arrays.binarySearch(hashes_idx, hash)-1; - if (idx == cache_size) { - return null; - } - while ((hashes_idx[idx] & HASH_MASK) == hash) { - int i = (int) (hashes_idx[idx] & INDEX_MASK) - 1; - if (Arrays.equals(encoding, encodings[i])) { - return cache[i]; - } - idx++; - if (idx == cache_size) { - return null; - } - } - return null; - } - - /** - * Puts the object into the cache. - * @param hash hash code for the prefix of the encoding - * @param encoding the encoded form of the object - * @param object the object to be saved in the cache - */ - public void put(long hash, byte[] encoding, Object object) { - // check for empty space in the cache - if (last_cached == cache_size) { - // so cache is full, will erase the first entry in the - // cache (oldest entry). it could be better to throw out - // rarely used value instead of oldest one.. - last_cached = 0; - cache_is_full = true; - } - // index pointing to the item of the table to be overwritten - int index = last_cached++; - - // improve the hash value with info from the tail of encoding - hash |= getSuffHash(encoding); - - if (cache_is_full) { - // indexing hash value to be overwritten: - long idx_hash = (hashes[index] | (index+1)); - int idx = Arrays.binarySearch(hashes_idx, idx_hash); - if (idx < 0) { - // it will never happen because we use saved hash value - // (hashes[index]) - System.out.println("WARNING! "+idx); - idx = -(idx + 1); - } - long new_hash_idx = (hash | (index + 1)); - int new_idx = Arrays.binarySearch(hashes_idx, new_hash_idx); - if (new_idx >= 0) { - // it's possible when we write the same hash in the same cell - if (idx != new_idx) { - // it will never happen because we use the same - // hash and the same index in hash table - System.out.println("WARNING: "); - System.out.println(">> idx: "+idx+" new_idx: "+new_idx); - } - } else { - new_idx = -(new_idx + 1); - // replace in sorted array - if (new_idx > idx) { - System.arraycopy(hashes_idx, idx+1, hashes_idx, idx, - new_idx - idx - 1); - hashes_idx[new_idx-1] = new_hash_idx; - } else if (idx > new_idx) { - System.arraycopy(hashes_idx, new_idx, hashes_idx, new_idx+1, - idx - new_idx); - hashes_idx[new_idx] = new_hash_idx; - } else { // idx == new_idx - hashes_idx[new_idx] = new_hash_idx; - } - } - } else { - long idx_hash = (hash | (index + 1)); - int idx = Arrays.binarySearch(hashes_idx, idx_hash); - if (idx < 0) { - // it will always be true because idx_hash depends on index - idx = -(idx + 1); - } - idx = idx - 1; - if (idx != cache_size - index - 1) { - // if not in the cell containing 0 (free cell), do copy: - System.arraycopy(hashes_idx, cache_size - index, - hashes_idx, cache_size - index - 1, - idx - (cache_size - index) + 1); - } - hashes_idx[idx] = idx_hash; - } - // overwrite the values in the tables: - hashes[index] = hash; - encodings[index] = encoding; - cache[index] = object; - } - - // Returns the hash code built on the base of the tail of the encoded form - // @param arr - the array containing at least prefix_size trailing bytes - // of encoded form - private long getSuffHash(byte[] arr) { - long hash_addon = 0; - for (int i=arr.length-1; i>arr.length - prefix_size; i--) { - hash_addon += (arr[i] & 0xFF); - } - return hash_addon << 16; - } - -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/DRLCertFactory.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/DRLCertFactory.java deleted file mode 100644 index 790be67..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/DRLCertFactory.java +++ /dev/null @@ -1,44 +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. - */ - -/** -* @author Alexander Y. Kleymenov -* @version $Revision$ -*/ - -package org.apache.harmony.security.provider.cert; - -import java.security.Provider; - -public final class DRLCertFactory extends Provider { - /** - * @serial - */ - private static final long serialVersionUID = -7269650779605195879L; - - /** - * Constructs the instance of the certificate factory provider. - */ - public DRLCertFactory() { - // specification of the provider name, version, and description. - super("DRLCertFactory", 1.0, "ASN.1, DER, PkiPath, PKCS7"); - // register the service - put("CertificateFactory.X509", "org.apache.harmony.security.provider.cert.X509CertFactoryImpl"); - // mapping the alias - put("Alg.Alias.CertificateFactory.X.509", "X509"); - } -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLEntryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLEntryImpl.java deleted file mode 100644 index 38500e5..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLEntryImpl.java +++ /dev/null @@ -1,179 +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. - */ - -/** -* @author Alexander Y. Kleymenov -* @version $Revision$ -*/ - -package org.apache.harmony.security.provider.cert; - -import java.math.BigInteger; -import java.security.cert.CRLException; -import java.security.cert.X509CRLEntry; -import java.util.Date; -import java.util.Set; -import javax.security.auth.x500.X500Principal; -import org.apache.harmony.security.x509.Extension; -import org.apache.harmony.security.x509.Extensions; -import org.apache.harmony.security.x509.TBSCertList; - -/** - * Implementation of X509CRLEntry. It wraps the instance - * of org.apache.harmony.security.x509.TBSCertList.RevokedCertificate - * obtained during the decoding of TBSCertList substructure - * of the CertificateList structure which is an X.509 form of CRL. - * (see RFC 3280 at http://www.ietf.org/rfc/rfc3280.txt) - * Normally the instances of this class are constructed by involving - * X509CRLImpl object. - * @see org.apache.harmony.security.x509.TBSCertList - * @see org.apache.harmony.security.provider.cert.X509CRLImpl - * @see java.security.cert.X509CRLEntry - */ -public class X509CRLEntryImpl extends X509CRLEntry { - - // the crl entry object to be wrapped in X509CRLEntry - private final TBSCertList.RevokedCertificate rcert; - // the extensions of the entry - private final Extensions extensions; - // issuer of the revoked certificate described by this crl entry - private final X500Principal issuer; - - // encoded form of this revoked certificate entry - private byte[] encoding; - - /** - * Creates an instance on the base of existing - * <code>TBSCertList.RevokedCertificate</code> object and - * information about the issuer of revoked certificate. - * If specified issuer is null, it is supposed that issuer - * of the revoked certificate is the same as for involving CRL. - */ - public X509CRLEntryImpl(TBSCertList.RevokedCertificate rcert, - X500Principal issuer) { - this.rcert = rcert; - this.extensions = rcert.getCrlEntryExtensions(); - this.issuer = issuer; - } - - // --------------------------------------------------------------------- - // ------ java.security.cert.X509CRLEntry method implementations ------- - // --------------------------------------------------------------------- - - /** - * @see java.security.cert.X509CRLEntry#getEncoded() - * method documentation for more info - */ - public byte[] getEncoded() throws CRLException { - if (encoding == null) { - encoding = rcert.getEncoded(); - } - byte[] result = new byte[encoding.length]; - System.arraycopy(encoding, 0, result, 0, encoding.length); - return result; - } - - /** - * @see java.security.cert.X509CRLEntry#getSerialNumber() - * method documentation for more info - */ - public BigInteger getSerialNumber() { - return rcert.getUserCertificate(); - } - - /** - * @see java.security.cert.X509CRLEntry#getCertificateIssuer() - * method documentation for more info - */ - public X500Principal getCertificateIssuer() { - return issuer; - } - - /** - * @see java.security.cert.X509CRLEntry#getRevocationDate() - * method documentation for more info - */ - public Date getRevocationDate() { - return rcert.getRevocationDate(); - } - - /** - * @see java.security.cert.X509CRLEntry#hasExtensions() - * method documentation for more info - */ - public boolean hasExtensions() { - return (extensions != null) && (extensions.size() != 0); - } - - /** - * @see java.security.cert.X509CRLEntry#toString() - * method documentation for more info - */ - public String toString() { - return "X509CRLEntryImpl: "+rcert.toString(); - } - - // --------------------------------------------------------------------- - // ------ java.security.cert.X509Extension method implementations ------ - // --------------------------------------------------------------------- - - /** - * @see java.security.cert.X509Extension#getNonCriticalExtensionOIDs() - * method documentation for more info - */ - public Set getNonCriticalExtensionOIDs() { - if (extensions == null) { - return null; - } - return extensions.getNonCriticalExtensions(); - } - - /** - * @see java.security.cert.X509Extension#getCriticalExtensionOIDs() - * method documentation for more info - */ - public Set getCriticalExtensionOIDs() { - if (extensions == null) { - return null; - } - return extensions.getCriticalExtensions(); - } - - /** - * @see java.security.cert.X509Extension#getExtensionValue(String) - * method documentation for more info - */ - public byte[] getExtensionValue(String oid) { - if (extensions == null) { - return null; - } - Extension ext = extensions.getExtensionByOID(oid); - return (ext == null) ? null : ext.getRawExtnValue(); - } - - /** - * @see java.security.cert.X509Extension#hasUnsupportedCriticalExtension() - * method documentation for more info - */ - public boolean hasUnsupportedCriticalExtension() { - if (extensions == null) { - return false; - } - return extensions.hasUnsupportedCritical(); - } -} - diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLImpl.java deleted file mode 100644 index 68ec38a..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CRLImpl.java +++ /dev/null @@ -1,504 +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. - */ - -/** -* @author Alexander Y. Kleymenov -* @version $Revision$ -*/ - -package org.apache.harmony.security.provider.cert; - -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Principal; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.cert.CRLException; -import java.security.cert.Certificate; -import java.security.cert.X509CRL; -import java.security.cert.X509CRLEntry; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.security.auth.x500.X500Principal; -import org.apache.harmony.security.utils.AlgNameMapper; -import org.apache.harmony.security.x509.CertificateList; -import org.apache.harmony.security.x509.Extension; -import org.apache.harmony.security.x509.Extensions; -import org.apache.harmony.security.x509.TBSCertList; - -/** - * This class is an implementation of X509CRL. It wraps - * the instance of org.apache.harmony.security.x509.CertificateList - * built on the base of provided ASN.1 DER encoded form of - * CertificateList structure (as specified in RFC 3280 - * http://www.ietf.org/rfc/rfc3280.txt). - * Implementation supports work with indirect CRLs. - * @see org.apache.harmony.security.x509.CertificateList - * @see java.security.cert.X509CRL - */ -public class X509CRLImpl extends X509CRL { - - // the core object to be wrapped in X509CRL - private final CertificateList crl; - - // To speed up access to the info, the following fields - // cache values retrieved from the CertificateList object - private final TBSCertList tbsCertList; - private byte[] tbsCertListEncoding; - private final Extensions extensions; - private X500Principal issuer; - private ArrayList entries; - private int entriesSize; - private byte[] signature; - private String sigAlgOID; - private String sigAlgName; - private byte[] sigAlgParams; - - // encoded form of crl - private byte[] encoding; - - // indicates whether the signature algorithm parameters are null - private boolean nullSigAlgParams; - // indicates whether the crl entries have already been retrieved - // from CertificateList object (crl) - private boolean entriesRetrieved; - - // indicates whether this X.509 CRL is direct or indirect - // (see rfc 3280 http://www.ietf.org/rfc/rfc3280.txt, p 5.) - private boolean isIndirectCRL; - // if crl is indirect, this field holds an info about how - // many of the leading certificates in the list are issued - // by the same issuer as CRL. - private int nonIndirectEntriesSize; - - /** - * Creates X.509 CRL by wrapping of the specified CertificateList object. - */ - public X509CRLImpl(CertificateList crl) { - this.crl = crl; - this.tbsCertList = crl.getTbsCertList(); - this.extensions = tbsCertList.getCrlExtensions(); - } - - /** - * Creates X.509 CRL on the base of ASN.1 DER encoded form of - * the CRL (CertificateList structure described in RFC 3280) - * provided via input stream. - * @throws CRLException if decoding errors occur. - */ - public X509CRLImpl(InputStream in) throws CRLException { - try { - // decode CertificateList structure - this.crl = (CertificateList) CertificateList.ASN1.decode(in); - this.tbsCertList = crl.getTbsCertList(); - this.extensions = tbsCertList.getCrlExtensions(); - } catch (IOException e) { - throw new CRLException(e); - } - } - - /** - * Creates X.509 CRL on the base of ASN.1 DER encoded form of - * the CRL (CertificateList structure described in RFC 3280) - * provided via array of bytes. - * @throws IOException if decoding errors occur. - */ - public X509CRLImpl(byte[] encoding) throws IOException { - this((CertificateList) CertificateList.ASN1.decode(encoding)); - } - - // --------------------------------------------------------------------- - // ----- java.security.cert.X509CRL abstract method implementations ---- - // --------------------------------------------------------------------- - - /** - * @see java.security.cert.X509CRL#getEncoded() - * method documentation for more info - */ - public byte[] getEncoded() throws CRLException { - if (encoding == null) { - encoding = crl.getEncoded(); - } - byte[] result = new byte[encoding.length]; - System.arraycopy(encoding, 0, result, 0, encoding.length); - return result; - } - - /** - * @see java.security.cert.X509CRL#getVersion() - * method documentation for more info - */ - public int getVersion() { - return tbsCertList.getVersion(); - } - - /** - * @see java.security.cert.X509CRL#getIssuerDN() - * method documentation for more info - */ - public Principal getIssuerDN() { - if (issuer == null) { - issuer = tbsCertList.getIssuer().getX500Principal(); - } - return issuer; - } - - /** - * @see java.security.cert.X509CRL#getIssuerX500Principal() - * method documentation for more info - */ - public X500Principal getIssuerX500Principal() { - if (issuer == null) { - issuer = tbsCertList.getIssuer().getX500Principal(); - } - return issuer; - } - - /** - * @see java.security.cert.X509CRL#getThisUpdate() - * method documentation for more info - */ - public Date getThisUpdate() { - return tbsCertList.getThisUpdate(); - } - - /** - * @see java.security.cert.X509CRL#getNextUpdate() - * method documentation for more info - */ - public Date getNextUpdate() { - return tbsCertList.getNextUpdate(); - } - - /* - * Retrieves the crl entries (TBSCertList.RevokedCertificate objects) - * from the TBSCertList structure and converts them to the - * X509CRLEntryImpl objects - */ - private void retrieveEntries() { - entriesRetrieved = true; - List rcerts = tbsCertList.getRevokedCertificates(); - if (rcerts == null) { - return; - } - entriesSize = rcerts.size(); - entries = new ArrayList(entriesSize); - // null means that revoked certificate issuer is the same as CRL issuer - X500Principal rcertIssuer = null; - for (int i=0; i<entriesSize; i++) { - TBSCertList.RevokedCertificate rcert = - (TBSCertList.RevokedCertificate) rcerts.get(i); - X500Principal iss = rcert.getIssuer(); - if (iss != null) { - // certificate issuer differs from CRL issuer - // and CRL is indirect. - rcertIssuer = iss; - isIndirectCRL = true; - // remember how many leading revoked certificates in the - // list are issued by the same issuer as issuer of CRL - // (these certificates are first in the list) - nonIndirectEntriesSize = i; - } - entries.add(new X509CRLEntryImpl(rcert, rcertIssuer)); - } - } - - /** - * Searches for certificate in CRL. - * This method supports indirect CRLs: if CRL is indirect method takes - * into account serial number and issuer of the certificate, - * if CRL issued by CA (i.e. it is not indirect) search is done only - * by serial number of the specified certificate. - * @see java.security.cert.X509CRL#getRevokedCertificate(X509Certificate) - * method documentation for more info - */ - public X509CRLEntry getRevokedCertificate(X509Certificate certificate) { - if (certificate == null) { - throw new NullPointerException("certificate == null"); - } - if (!entriesRetrieved) { - retrieveEntries(); - } - if (entries == null) { - return null; - } - BigInteger serialN = certificate.getSerialNumber(); - if (isIndirectCRL) { - // search in indirect crl - X500Principal certIssuer = certificate.getIssuerX500Principal(); - if (certIssuer.equals(getIssuerX500Principal())) { - // certificate issuer is CRL issuer - certIssuer = null; - } - for (int i=0; i<entriesSize; i++) { - X509CRLEntry entry = (X509CRLEntry) entries.get(i); - // check the serial number of revoked certificate - if (serialN.equals(entry.getSerialNumber())) { - // revoked certificate issuer - X500Principal iss = entry.getCertificateIssuer(); - // check the issuer of revoked certificate - if (certIssuer != null) { - // certificate issuer is not a CRL issuer, so - // check issuers for equality - if (certIssuer.equals(iss)) { - return entry; - } - } else if (iss == null) { - // both certificates was issued by CRL issuer - return entry; - } - } - } - } else { - // search in CA's (non indirect) crl: just look up the serial number - for (int i=0; i<entriesSize; i++) { - X509CRLEntry entry = (X509CRLEntry) entries.get(i); - if (serialN.equals(entry.getSerialNumber())) { - return entry; - } - } - } - return null; - } - - /** - * Method searches for CRL entry with specified serial number. - * The method will search only certificate issued by CRL's issuer. - * @see java.security.cert.X509CRL#getRevokedCertificate(BigInteger) - * method documentation for more info - */ - public X509CRLEntry getRevokedCertificate(BigInteger serialNumber) { - if (!entriesRetrieved) { - retrieveEntries(); - } - if (entries == null) { - return null; - } - for (int i=0; i<nonIndirectEntriesSize; i++) { - X509CRLEntry entry = (X509CRLEntry) entries.get(i); - if (serialNumber.equals(entry.getSerialNumber())) { - return entry; - } - } - return null; - } - - /** - * @see java.security.cert.X509CRL#getRevokedCertificates() - * method documentation for more info - */ - public Set<? extends X509CRLEntry> getRevokedCertificates() { - if (!entriesRetrieved) { - retrieveEntries(); - } - if (entries == null) { - return null; - } - return new HashSet(entries); - } - - /** - * @see java.security.cert.X509CRL#getTBSCertList() - * method documentation for more info - */ - public byte[] getTBSCertList() throws CRLException { - if (tbsCertListEncoding == null) { - tbsCertListEncoding = tbsCertList.getEncoded(); - } - byte[] result = new byte[tbsCertListEncoding.length]; - System.arraycopy(tbsCertListEncoding, 0, - result, 0, tbsCertListEncoding.length); - return result; - } - - /** - * @see java.security.cert.X509CRL#getSignature() - * method documentation for more info - */ - public byte[] getSignature() { - if (signature == null) { - signature = crl.getSignatureValue(); - } - byte[] result = new byte[signature.length]; - System.arraycopy(signature, 0, result, 0, signature.length); - return result; - } - - /** - * @see java.security.cert.X509CRL#getSigAlgName() - * method documentation for more info - */ - public String getSigAlgName() { - if (sigAlgOID == null) { - sigAlgOID = tbsCertList.getSignature().getAlgorithm(); - sigAlgName = AlgNameMapper.map2AlgName(sigAlgOID); - if (sigAlgName == null) { - sigAlgName = sigAlgOID; - } - } - return sigAlgName; - } - - /** - * @see java.security.cert.X509CRL#getSigAlgOID() - * method documentation for more info - */ - public String getSigAlgOID() { - if (sigAlgOID == null) { - sigAlgOID = tbsCertList.getSignature().getAlgorithm(); - sigAlgName = AlgNameMapper.map2AlgName(sigAlgOID); - if (sigAlgName == null) { - sigAlgName = sigAlgOID; - } - } - return sigAlgOID; - } - - /** - * @see java.security.cert.X509CRL#getSigAlgParams() - * method documentation for more info - */ - public byte[] getSigAlgParams() { - if (nullSigAlgParams) { - return null; - } - if (sigAlgParams == null) { - sigAlgParams = tbsCertList.getSignature().getParameters(); - if (sigAlgParams == null) { - nullSigAlgParams = true; - return null; - } - } - return sigAlgParams; - } - - /** - * @see java.security.cert.X509CRL#verify(PublicKey key) - * method documentation for more info - */ - public void verify(PublicKey key) - throws CRLException, NoSuchAlgorithmException, - InvalidKeyException, NoSuchProviderException, - SignatureException { - Signature signature = Signature.getInstance(getSigAlgName()); - signature.initVerify(key); - byte[] tbsEncoding = tbsCertList.getEncoded(); - signature.update(tbsEncoding, 0, tbsEncoding.length); - if (!signature.verify(crl.getSignatureValue())) { - throw new SignatureException("Signature was not verified"); - } - } - - /** - * @see java.security.cert.X509CRL#verify(PublicKey key, String sigProvider) - * method documentation for more info - */ - public void verify(PublicKey key, String sigProvider) - throws CRLException, NoSuchAlgorithmException, - InvalidKeyException, NoSuchProviderException, - SignatureException { - Signature signature = Signature.getInstance( - getSigAlgName(), sigProvider); - signature.initVerify(key); - byte[] tbsEncoding = tbsCertList.getEncoded(); - signature.update(tbsEncoding, 0, tbsEncoding.length); - if (!signature.verify(crl.getSignatureValue())) { - throw new SignatureException("Signature was not verified"); - } - } - - // --------------------------------------------------------------------- - // ------ java.security.cert.CRL abstract method implementations ------- - // --------------------------------------------------------------------- - - /** - * @see java.security.cert.CRL#isRevoked(Certificate) - * method documentation for more info - */ - public boolean isRevoked(Certificate cert) { - if (!(cert instanceof X509Certificate)) { - return false; - } - return getRevokedCertificate((X509Certificate) cert) != null; - } - - /** - * @see java.security.cert.CRL#toString() - * method documentation for more info - */ - public String toString() { - return crl.toString(); - } - - // --------------------------------------------------------------------- - // ------ java.security.cert.X509Extension method implementations ------ - // --------------------------------------------------------------------- - - /** - * @see java.security.cert.X509Extension#getNonCriticalExtensionOIDs() - * method documentation for more info - */ - public Set getNonCriticalExtensionOIDs() { - if (extensions == null) { - return null; - } - return extensions.getNonCriticalExtensions(); - } - - /** - * @see java.security.cert.X509Extension#getCriticalExtensionOIDs() - * method documentation for more info - */ - public Set getCriticalExtensionOIDs() { - if (extensions == null) { - return null; - } - return extensions.getCriticalExtensions(); - } - - /** - * @see java.security.cert.X509Extension#getExtensionValue(String) - * method documentation for more info - */ - public byte[] getExtensionValue(String oid) { - if (extensions == null) { - return null; - } - Extension ext = extensions.getExtensionByOID(oid); - return (ext == null) ? null : ext.getRawExtnValue(); - } - - /** - * @see java.security.cert.X509Extension#hasUnsupportedCriticalExtension() - * method documentation for more info - */ - public boolean hasUnsupportedCriticalExtension() { - if (extensions == null) { - return false; - } - return extensions.hasUnsupportedCritical(); - } -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java deleted file mode 100644 index 9129ec2..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java +++ /dev/null @@ -1,858 +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. - */ - -/** -* @author Alexander Y. Kleymenov -* @version $Revision$ -*/ - -package org.apache.harmony.security.provider.cert; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.security.cert.CRL; -import java.security.cert.CRLException; -import java.security.cert.CertPath; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactorySpi; -import java.security.cert.X509CRL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import libcore.io.Base64; -import libcore.io.Streams; -import org.apache.harmony.security.asn1.ASN1Constants; -import org.apache.harmony.security.asn1.BerInputStream; -import org.apache.harmony.security.pkcs7.ContentInfo; -import org.apache.harmony.security.pkcs7.SignedData; -import org.apache.harmony.security.x509.CertificateList; - -/** - * X509 Certificate Factory Service Provider Interface Implementation. - * It supports CRLs and Certificates in (PEM) ASN.1 DER encoded form, - * and Certification Paths in PkiPath and PKCS7 formats. - * For Certificates and CRLs factory maintains the caching - * mechanisms allowing to speed up repeated Certificate/CRL - * generation. - * @see Cache - */ -public class X509CertFactoryImpl extends CertificateFactorySpi { - - // number of leading/trailing bytes used for cert hash computation - private static final int CERT_CACHE_SEED_LENGTH = 28; - // certificate cache - private static final Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH); - // number of leading/trailing bytes used for crl hash computation - private static final int CRL_CACHE_SEED_LENGTH = 24; - // crl cache - private static final Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH); - - /** - * Default constructor. - * Creates the instance of Certificate Factory SPI ready for use. - */ - public X509CertFactoryImpl() { } - - /** - * Generates the X.509 certificate from the data in the stream. - * The data in the stream can be either in ASN.1 DER encoded X.509 - * certificate, or PEM (Base64 encoding bounded by - * <code>"-----BEGIN CERTIFICATE-----"</code> at the beginning and - * <code>"-----END CERTIFICATE-----"</code> at the end) representation - * of the former encoded form. - * - * Before the generation the encoded form is looked up in - * the cache. If the cache contains the certificate with requested encoded - * form it is returned from it, otherwise it is generated by ASN.1 - * decoder. - * - * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificate(InputStream) - * method documentation for more info - */ - public Certificate engineGenerateCertificate(InputStream inStream) - throws CertificateException { - if (inStream == null) { - throw new CertificateException("inStream == null"); - } - try { - if (!inStream.markSupported()) { - // create the mark supporting wrapper - inStream = new RestoringInputStream(inStream); - } - // mark is needed to recognize the format of the provided encoding - // (ASN.1 or PEM) - inStream.mark(1); - // check whether the provided certificate is in PEM encoded form - if (inStream.read() == '-') { - // decode PEM, retrieve CRL - return getCertificate(decodePEM(inStream, CERT_BOUND_SUFFIX)); - } else { - inStream.reset(); - // retrieve CRL - return getCertificate(inStream); - } - } catch (IOException e) { - throw new CertificateException(e); - } - } - - /** - * Generates the collection of the certificates on the base of provided - * via input stream encodings. - * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificates(InputStream) - * method documentation for more info - */ - public Collection<? extends Certificate> - engineGenerateCertificates(InputStream inStream) - throws CertificateException { - if (inStream == null) { - throw new CertificateException("inStream == null"); - } - ArrayList<Certificate> result = new ArrayList<Certificate>(); - try { - if (!inStream.markSupported()) { - // create the mark supporting wrapper - inStream = new RestoringInputStream(inStream); - } - // if it is PEM encoded form this array will contain the encoding - // so ((it is PEM) <-> (encoding != null)) - byte[] encoding = null; - // The following by SEQUENCE ASN.1 tag, used for - // recognizing the data format - // (is it PKCS7 ContentInfo structure, X.509 Certificate, or - // unsupported encoding) - int second_asn1_tag = -1; - inStream.mark(1); - int ch; - while ((ch = inStream.read()) != -1) { - // check if it is PEM encoded form - if (ch == '-') { // beginning of PEM encoding ('-' char) - // decode PEM chunk and store its content (ASN.1 encoding) - encoding = decodePEM(inStream, FREE_BOUND_SUFFIX); - } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30) - encoding = null; - inStream.reset(); - // prepare for data format determination - inStream.mark(CERT_CACHE_SEED_LENGTH); - } else { // unsupported data - if (result.size() == 0) { - throw new CertificateException("Unsupported encoding"); - } else { - // it can be trailing user data, - // so keep it in the stream - inStream.reset(); - return result; - } - } - // Check the data format - BerInputStream in = (encoding == null) - ? new BerInputStream(inStream) - : new BerInputStream(encoding); - // read the next ASN.1 tag - second_asn1_tag = in.next(); // inStream position changed - if (encoding == null) { - // keep whole structure in the stream - inStream.reset(); - } - // check if it is a TBSCertificate structure - if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) { - if (result.size() == 0) { - // there were not read X.509 Certificates, so - // break the cycle and check - // whether it is PKCS7 structure - break; - } else { - // it can be trailing user data, - // so return what we already read - return result; - } - } else { - if (encoding == null) { - result.add(getCertificate(inStream)); - } else { - result.add(getCertificate(encoding)); - } - } - // mark for the next iteration - inStream.mark(1); - } - if (result.size() != 0) { - // some Certificates have been read - return result; - } else if (ch == -1) { - /* No data in the stream, so return the empty collection. */ - return result; - } - // else: check if it is PKCS7 - if (second_asn1_tag == ASN1Constants.TAG_OID) { - // it is PKCS7 ContentInfo structure, so decode it - ContentInfo info = (ContentInfo) - ((encoding != null) - ? ContentInfo.ASN1.decode(encoding) - : ContentInfo.ASN1.decode(inStream)); - // retrieve SignedData - SignedData data = info.getSignedData(); - if (data == null) { - throw new CertificateException("Invalid PKCS7 data provided"); - } - List<org.apache.harmony.security.x509.Certificate> certs = data.getCertificates(); - if (certs != null) { - for (org.apache.harmony.security.x509.Certificate cert : certs) { - result.add(new X509CertImpl(cert)); - } - } - return result; - } - // else: Unknown data format - throw new CertificateException("Unsupported encoding"); - } catch (IOException e) { - throw new CertificateException(e); - } - } - - /** - * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream) - * method documentation for more info - */ - public CRL engineGenerateCRL(InputStream inStream) - throws CRLException { - if (inStream == null) { - throw new CRLException("inStream == null"); - } - try { - if (!inStream.markSupported()) { - // Create the mark supporting wrapper - // Mark is needed to recognize the format - // of provided encoding form (ASN.1 or PEM) - inStream = new RestoringInputStream(inStream); - } - inStream.mark(1); - // check whether the provided crl is in PEM encoded form - if (inStream.read() == '-') { - // decode PEM, retrieve CRL - return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX)); - } else { - inStream.reset(); - // retrieve CRL - return getCRL(inStream); - } - } catch (IOException e) { - throw new CRLException(e); - } - } - - /** - * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream) - * method documentation for more info - */ - public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) - throws CRLException { - if (inStream == null) { - throw new CRLException("inStream == null"); - } - ArrayList<CRL> result = new ArrayList<CRL>(); - try { - if (!inStream.markSupported()) { - inStream = new RestoringInputStream(inStream); - } - // if it is PEM encoded form this array will contain the encoding - // so ((it is PEM) <-> (encoding != null)) - byte[] encoding = null; - // The following by SEQUENCE ASN.1 tag, used for - // recognizing the data format - // (is it PKCS7 ContentInfo structure, X.509 CRL, or - // unsupported encoding) - int second_asn1_tag = -1; - inStream.mark(1); - int ch; - while ((ch = inStream.read()) != -1) { - // check if it is PEM encoded form - if (ch == '-') { // beginning of PEM encoding ('-' char) - // decode PEM chunk and store its content (ASN.1 encoding) - encoding = decodePEM(inStream, FREE_BOUND_SUFFIX); - } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30) - encoding = null; - inStream.reset(); - // prepare for data format determination - inStream.mark(CRL_CACHE_SEED_LENGTH); - } else { // unsupported data - if (result.size() == 0) { - throw new CRLException("Unsupported encoding"); - } else { - // it can be trailing user data, - // so keep it in the stream - inStream.reset(); - return result; - } - } - // Check the data format - BerInputStream in = (encoding == null) - ? new BerInputStream(inStream) - : new BerInputStream(encoding); - // read the next ASN.1 tag - second_asn1_tag = in.next(); - if (encoding == null) { - // keep whole structure in the stream - inStream.reset(); - } - // check if it is a TBSCertList structure - if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) { - if (result.size() == 0) { - // there were not read X.509 CRLs, so - // break the cycle and check - // whether it is PKCS7 structure - break; - } else { - // it can be trailing user data, - // so return what we already read - return result; - } - } else { - if (encoding == null) { - result.add(getCRL(inStream)); - } else { - result.add(getCRL(encoding)); - } - } - inStream.mark(1); - } - if (result.size() != 0) { - // the stream was read out - return result; - } else if (ch == -1) { - throw new CRLException("There is no data in the stream"); - } - // else: check if it is PKCS7 - if (second_asn1_tag == ASN1Constants.TAG_OID) { - // it is PKCS7 ContentInfo structure, so decode it - ContentInfo info = (ContentInfo) - ((encoding != null) - ? ContentInfo.ASN1.decode(encoding) - : ContentInfo.ASN1.decode(inStream)); - // retrieve SignedData - SignedData data = info.getSignedData(); - if (data == null) { - throw new CRLException("Invalid PKCS7 data provided"); - } - List<CertificateList> crls = data.getCRLs(); - if (crls != null) { - for (CertificateList crl : crls) { - result.add(new X509CRLImpl(crl)); - } - } - return result; - } - // else: Unknown data format - throw new CRLException("Unsupported encoding"); - } catch (IOException e) { - throw new CRLException(e); - } - } - - /** - * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream) - * method documentation for more info - */ - public CertPath engineGenerateCertPath(InputStream inStream) - throws CertificateException { - if (inStream == null) { - throw new CertificateException("inStream == null"); - } - return engineGenerateCertPath(inStream, "PkiPath"); - } - - /** - * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String) - * method documentation for more info - */ - public CertPath engineGenerateCertPath( - InputStream inStream, String encoding) throws CertificateException { - if (inStream == null) { - throw new CertificateException("inStream == null"); - } - if (!inStream.markSupported()) { - inStream = new RestoringInputStream(inStream); - } - try { - inStream.mark(1); - int ch; - - // check if it is PEM encoded form - if ((ch = inStream.read()) == '-') { - // decode PEM chunk into ASN.1 form and decode CertPath object - return X509CertPathImpl.getInstance( - decodePEM(inStream, FREE_BOUND_SUFFIX), encoding); - } else if (ch == 0x30) { // ASN.1 Sequence - inStream.reset(); - // decode ASN.1 form - return X509CertPathImpl.getInstance(inStream, encoding); - } else { - throw new CertificateException("Unsupported encoding"); - } - } catch (IOException e) { - throw new CertificateException(e); - } - } - - /** - * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List) - * method documentation for more info - */ - public CertPath engineGenerateCertPath(List<? extends Certificate> certificates) - throws CertificateException { - return new X509CertPathImpl(certificates); - } - - /** - * @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings() - * method documentation for more info - */ - public Iterator<String> engineGetCertPathEncodings() { - return X509CertPathImpl.encodings.iterator(); - } - - // --------------------------------------------------------------------- - // ------------------------ Staff methods ------------------------------ - // --------------------------------------------------------------------- - - private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(StandardCharsets.UTF_8); - private static final byte[] PEM_END = "-----END".getBytes(StandardCharsets.UTF_8); - /** - * Code describing free format for PEM boundary suffix: - * "^-----BEGIN.*\n" at the beginning, and<br> - * "\n-----END.*(EOF|\n)$" at the end. - */ - private static final byte[] FREE_BOUND_SUFFIX = null; - /** - * Code describing PEM boundary suffix for X.509 certificate: - * "^-----BEGIN CERTIFICATE-----\n" at the beginning, and<br> - * "\n-----END CERTIFICATE-----" at the end. - */ - private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(StandardCharsets.UTF_8); - - /** - * Method retrieves the PEM encoded data from the stream - * and returns its decoded representation. - * Method checks correctness of PEM boundaries. It supposes that - * the first '-' of the opening boundary has already been read from - * the stream. So first of all it checks that the leading bytes - * are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix - * is not null, it checks that next bytes equal to boundary_suffix - * + new line char[s] ([CR]LF). - * If boundary_suffix parameter is null, method supposes free suffix - * format and skips any bytes until the new line.<br> - * After the opening boundary has been read and checked, the method - * read Base64 encoded data until closing PEM boundary is not reached.<br> - * Than it checks closing boundary - it should start with new line + - * "-----END" + boundary_suffix. If boundary_suffix is null, - * any characters are skipped until the new line.<br> - * After this any trailing new line characters are skipped from the stream, - * Base64 encoding is decoded and returned. - * @param inStream the stream containing the PEM encoding. - * @param boundary_suffix the suffix of expected PEM multipart - * boundary delimiter.<br> - * If it is null, that any character sequences are accepted. - * @throws IOException If PEM boundary delimiter does not comply - * with expected or some I/O or decoding problems occur. - */ - private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix) - throws IOException { - int ch; // the char to be read - // check and skip opening boundary delimiter - // (first '-' is supposed as already read) - for (int i = 1; i < PEM_BEGIN.length; ++i) { - if (PEM_BEGIN[i] != (ch = inStream.read())) { - throw new IOException( - "Incorrect PEM encoding: '-----BEGIN" - + ((boundary_suffix == null) - ? "" : new String(boundary_suffix)) - + "' is expected as opening delimiter boundary."); - } - } - if (boundary_suffix == null) { - // read (skip) the trailing characters of - // the beginning PEM boundary delimiter - while ((ch = inStream.read()) != '\n') { - if (ch == -1) { - throw new IOException("Incorrect PEM encoding: EOF before content"); - } - } - } else { - for (int i=0; i<boundary_suffix.length; i++) { - if (boundary_suffix[i] != inStream.read()) { - throw new IOException("Incorrect PEM encoding: '-----BEGIN" + - new String(boundary_suffix) + "' is expected as opening delimiter boundary."); - } - } - // read new line characters - if ((ch = inStream.read()) == '\r') { - // CR has been read, now read LF character - ch = inStream.read(); - } - if (ch != '\n') { - throw new IOException("Incorrect PEM encoding: newline expected after " + - "opening delimiter boundary"); - } - } - int size = 1024; // the size of the buffer containing Base64 data - byte[] buff = new byte[size]; - int index = 0; - // read bytes while ending boundary delimiter is not reached - while ((ch = inStream.read()) != '-') { - if (ch == -1) { - throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter"); - } - buff[index++] = (byte) ch; - if (index == size) { - // enlarge the buffer - byte[] newbuff = new byte[size+1024]; - System.arraycopy(buff, 0, newbuff, 0, size); - buff = newbuff; - size += 1024; - } - } - if (buff[index-1] != '\n') { - throw new IOException("Incorrect Base64 encoding: newline expected before " + - "closing boundary delimiter"); - } - // check and skip closing boundary delimiter prefix - // (first '-' was read) - for (int i = 1; i < PEM_END.length; ++i) { - if (PEM_END[i] != inStream.read()) { - throw badEnd(boundary_suffix); - } - } - if (boundary_suffix == null) { - // read (skip) the trailing characters of - // the closing PEM boundary delimiter - while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) { - } - } else { - for (int i=0; i<boundary_suffix.length; i++) { - if (boundary_suffix[i] != inStream.read()) { - throw badEnd(boundary_suffix); - } - } - } - // skip trailing line breaks - inStream.mark(1); - while (((ch = inStream.read()) != -1) && (ch == '\n' || ch == '\r')) { - inStream.mark(1); - } - inStream.reset(); - buff = Base64.decode(buff, index); - if (buff == null) { - throw new IOException("Incorrect Base64 encoding"); - } - return buff; - } - - private IOException badEnd(byte[] boundary_suffix) throws IOException { - String s = (boundary_suffix == null) ? "" : new String(boundary_suffix); - throw new IOException("Incorrect PEM encoding: '-----END" + s + "' is expected as closing delimiter boundary."); - } - - /** - * Reads the data of specified length from source - * and returns it as an array. - * @return the byte array contained read data or - * null if the stream contains not enough data - * @throws IOException if some I/O error has been occurred. - */ - private static byte[] readBytes(InputStream source, int length) - throws IOException { - byte[] result = new byte[length]; - for (int i=0; i<length; i++) { - int bytik = source.read(); - if (bytik == -1) { - return null; - } - result[i] = (byte) bytik; - } - return result; - } - - /** - * Returns the Certificate object corresponding to the provided encoding. - * Resulting object is retrieved from the cache - * if it contains such correspondence - * and is constructed on the base of encoding - * and stored in the cache otherwise. - * @throws IOException if some decoding errors occur - * (in the case of cache miss). - */ - private static Certificate getCertificate(byte[] encoding) - throws CertificateException, IOException { - if (encoding.length < CERT_CACHE_SEED_LENGTH) { - throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH"); - } - synchronized (CERT_CACHE) { - long hash = CERT_CACHE.getHash(encoding); - if (CERT_CACHE.contains(hash)) { - Certificate res = - (Certificate) CERT_CACHE.get(hash, encoding); - if (res != null) { - return res; - } - } - Certificate res = new X509CertImpl(encoding); - CERT_CACHE.put(hash, encoding, res); - return res; - } - } - - /** - * Returns the Certificate object corresponding to the encoding provided - * by the stream. - * Resulting object is retrieved from the cache - * if it contains such correspondence - * and is constructed on the base of encoding - * and stored in the cache otherwise. - * @throws IOException if some decoding errors occur - * (in the case of cache miss). - */ - private static Certificate getCertificate(InputStream inStream) - throws CertificateException, IOException { - synchronized (CERT_CACHE) { - inStream.mark(CERT_CACHE_SEED_LENGTH); - // read the prefix of the encoding - byte[] buff = readBytes(inStream, CERT_CACHE_SEED_LENGTH); - inStream.reset(); - if (buff == null) { - throw new CertificateException("InputStream doesn't contain enough data"); - } - long hash = CERT_CACHE.getHash(buff); - if (CERT_CACHE.contains(hash)) { - byte[] encoding = new byte[BerInputStream.getLength(buff)]; - if (encoding.length < CERT_CACHE_SEED_LENGTH) { - throw new CertificateException("Bad Certificate encoding"); - } - Streams.readFully(inStream, encoding); - Certificate res = (Certificate) CERT_CACHE.get(hash, encoding); - if (res != null) { - return res; - } - res = new X509CertImpl(encoding); - CERT_CACHE.put(hash, encoding, res); - return res; - } else { - inStream.reset(); - Certificate res = new X509CertImpl(inStream); - CERT_CACHE.put(hash, res.getEncoded(), res); - return res; - } - } - } - - /** - * Returns the CRL object corresponding to the provided encoding. - * Resulting object is retrieved from the cache - * if it contains such correspondence - * and is constructed on the base of encoding - * and stored in the cache otherwise. - * @throws IOException if some decoding errors occur - * (in the case of cache miss). - */ - private static CRL getCRL(byte[] encoding) - throws CRLException, IOException { - if (encoding.length < CRL_CACHE_SEED_LENGTH) { - throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH"); - } - synchronized (CRL_CACHE) { - long hash = CRL_CACHE.getHash(encoding); - if (CRL_CACHE.contains(hash)) { - X509CRL res = (X509CRL) CRL_CACHE.get(hash, encoding); - if (res != null) { - return res; - } - } - X509CRL res = new X509CRLImpl(encoding); - CRL_CACHE.put(hash, encoding, res); - return res; - } - } - - /** - * Returns the CRL object corresponding to the encoding provided - * by the stream. - * Resulting object is retrieved from the cache - * if it contains such correspondence - * and is constructed on the base of encoding - * and stored in the cache otherwise. - * @throws IOException if some decoding errors occur - * (in the case of cache miss). - */ - private static CRL getCRL(InputStream inStream) - throws CRLException, IOException { - synchronized (CRL_CACHE) { - inStream.mark(CRL_CACHE_SEED_LENGTH); - byte[] buff = readBytes(inStream, CRL_CACHE_SEED_LENGTH); - // read the prefix of the encoding - inStream.reset(); - if (buff == null) { - throw new CRLException("InputStream doesn't contain enough data"); - } - long hash = CRL_CACHE.getHash(buff); - if (CRL_CACHE.contains(hash)) { - byte[] encoding = new byte[BerInputStream.getLength(buff)]; - if (encoding.length < CRL_CACHE_SEED_LENGTH) { - throw new CRLException("Bad CRL encoding"); - } - Streams.readFully(inStream, encoding); - CRL res = (CRL) CRL_CACHE.get(hash, encoding); - if (res != null) { - return res; - } - res = new X509CRLImpl(encoding); - CRL_CACHE.put(hash, encoding, res); - return res; - } else { - X509CRL res = new X509CRLImpl(inStream); - CRL_CACHE.put(hash, res.getEncoded(), res); - return res; - } - } - } - - /* - * This class extends any existing input stream with - * mark functionality. It acts as a wrapper over the - * stream and supports reset to the - * marked state with readlimit no more than BUFF_SIZE. - */ - private static class RestoringInputStream extends InputStream { - - // wrapped input stream - private final InputStream inStream; - // specifies how much of the read data is buffered - // after the mark has been set up - private static final int BUFF_SIZE = 32; - // buffer to keep the bytes read after the mark has been set up - private final int[] buff = new int[BUFF_SIZE*2]; - // position of the next byte to read, - // the value of -1 indicates that the buffer is not used - // (mark was not set up or was invalidated, or reset to the marked - // position has been done and all the buffered data was read out) - private int pos = -1; - // position of the last buffered byte - private int bar = 0; - // position in the buffer where the mark becomes invalidated - private int end = 0; - - /** - * Creates the mark supporting wrapper over the stream. - */ - public RestoringInputStream(InputStream inStream) { - this.inStream = inStream; - } - - @Override - public int available() throws IOException { - return (bar - pos) + inStream.available(); - } - - @Override - public void close() throws IOException { - inStream.close(); - } - - @Override - public void mark(int readlimit) { - if (pos < 0) { - pos = 0; - bar = 0; - end = BUFF_SIZE - 1; - } else { - end = (pos + BUFF_SIZE - 1) % BUFF_SIZE; - } - } - - @Override - public boolean markSupported() { - return true; - } - - /** - * Reads the byte from the stream. If mark has been set up - * and was not invalidated byte is read from the underlying - * stream and saved into the buffer. If the current read position - * has been reset to the marked position and there are remaining - * bytes in the buffer, the byte is taken from it. In the other cases - * (if mark has been invalidated, or there are no buffered bytes) - * the byte is taken directly from the underlying stream and it is - * returned without saving to the buffer. - * - * @see java.io.InputStream#read() - * method documentation for more info - */ - public int read() throws IOException { - // if buffer is currently used - if (pos >= 0) { - // current position in the buffer - int cur = pos % BUFF_SIZE; - // check whether the buffer contains the data to be read - if (cur < bar) { - // return the data from the buffer - pos++; - return buff[cur]; - } - // check whether buffer has free space - if (cur != end) { - // it has, so read the data from the wrapped stream - // and place it in the buffer - buff[cur] = inStream.read(); - bar = cur+1; - pos++; - return buff[cur]; - } else { - // buffer if full and can not operate - // any more, so invalidate the mark position - // and turn off the using of buffer - pos = -1; - } - } - // buffer is not used, so return the data from the wrapped stream - return inStream.read(); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int read_b; - int i; - for (i=0; i<len; i++) { - if ((read_b = read()) == -1) { - return (i == 0) ? -1 : i; - } - b[off+i] = (byte) read_b; - } - return i; - } - - @Override - public void reset() throws IOException { - if (pos >= 0) { - pos = (end + 1) % BUFF_SIZE; - } else { - throw new IOException("Could not reset the stream: " + - "position became invalid or stream has not been marked"); - } - } - } -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java deleted file mode 100644 index 4600bdc..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertImpl.java +++ /dev/null @@ -1,430 +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. - */ - -/** -* @author Alexander Y. Kleymenov -* @version $Revision$ -*/ - -package org.apache.harmony.security.provider.cert; - -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Principal; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.CertificateParsingException; -import java.security.cert.X509Certificate; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Set; -import javax.security.auth.x500.X500Principal; -import org.apache.harmony.security.utils.AlgNameMapper; -import org.apache.harmony.security.x509.Certificate; -import org.apache.harmony.security.x509.Extension; -import org.apache.harmony.security.x509.Extensions; -import org.apache.harmony.security.x509.TBSCertificate; - -/** - * This class is an implementation of X509Certificate. It wraps - * the instance of org.apache.harmony.security.x509.Certificate - * built on the base of provided ASN.1 DER encoded form of - * Certificate structure (as specified in RFC 3280 - * http://www.ietf.org/rfc/rfc3280.txt). - * @see org.apache.harmony.security.x509.Certificate - * @see java.security.cert.X509Certificate - */ -public final class X509CertImpl extends X509Certificate { - - /** @serial */ - private static final long serialVersionUID = 2972248729446736154L; - - /** the core object to be wrapped in X509Certificate */ - private final Certificate certificate; - - private final TBSCertificate tbsCert; - private final Extensions extensions; - // to speed up access to the info, the following fields - // cache values retrieved from the certificate object, - // initialized using the "single-check idiom". - private volatile long notBefore = -1; - private volatile long notAfter = -1; - private volatile BigInteger serialNumber; - private volatile X500Principal issuer; - private volatile X500Principal subject; - private volatile byte[] tbsCertificate; - private volatile byte[] signature; - private volatile String sigAlgName; - private volatile String sigAlgOID; - private volatile byte[] sigAlgParams; - // indicates whether the signature algorithm parameters are null - private volatile boolean nullSigAlgParams; - private volatile PublicKey publicKey; - - // encoding of the certificate - private volatile byte[] encoding; - - /** - * Constructs the instance on the base of ASN.1 encoded - * form of X.509 certificate provided via stream parameter. - * @param in input stream containing ASN.1 encoded form of certificate. - * @throws CertificateException if some decoding problems occur. - */ - public X509CertImpl(InputStream in) throws CertificateException { - try { - // decode the Certificate object - this.certificate = (Certificate) Certificate.ASN1.decode(in); - // cache the values of TBSCertificate and Extensions - this.tbsCert = certificate.getTbsCertificate(); - this.extensions = tbsCert.getExtensions(); - } catch (IOException e) { - throw new CertificateException(e); - } - } - - /** - * Constructs the instance on the base of existing Certificate object to - * be wrapped. - */ - public X509CertImpl(Certificate certificate) { - this.certificate = certificate; - // cache the values of TBSCertificate and Extensions - this.tbsCert = certificate.getTbsCertificate(); - this.extensions = tbsCert.getExtensions(); - } - - /** - * Constructs the instance on the base of ASN.1 encoded - * form of X.509 certificate provided via array of bytes. - * @param encoding byte array containing ASN.1 encoded form of certificate. - * @throws IOException if some decoding problems occur. - */ - public X509CertImpl(byte[] encoding) throws IOException { - this((Certificate) Certificate.ASN1.decode(encoding)); - } - - public void checkValidity() - throws CertificateExpiredException, CertificateNotYetValidException { - checkValidity(System.currentTimeMillis()); - } - - public void checkValidity(Date date) - throws CertificateExpiredException, CertificateNotYetValidException { - checkValidity(date.getTime()); - } - - private void checkValidity(long time) - throws CertificateExpiredException, CertificateNotYetValidException { - if (time < getNotBeforeInternal()) { - throw new CertificateNotYetValidException("current time: " + new Date(time) - + ", validation time: " + new Date(getNotBeforeInternal())); - } - if (time > getNotAfterInternal()) { - throw new CertificateExpiredException("current time: " + new Date(time) - + ", expiration time: " + new Date(getNotAfterInternal())); - } - } - - public int getVersion() { - return tbsCert.getVersion() + 1; - } - - public BigInteger getSerialNumber() { - BigInteger result = serialNumber; - if (result == null) { - serialNumber = result = tbsCert.getSerialNumber(); - } - return result; - } - - public Principal getIssuerDN() { - return getIssuerX500Principal(); - } - - public X500Principal getIssuerX500Principal() { - X500Principal result = issuer; - if (result == null) { - // retrieve the issuer's principal - issuer = result = tbsCert.getIssuer().getX500Principal(); - } - return result; - } - - public Principal getSubjectDN() { - return getSubjectX500Principal(); - } - - public X500Principal getSubjectX500Principal() { - X500Principal result = subject; - if (result == null) { - // retrieve the subject's principal - subject = result = tbsCert.getSubject().getX500Principal(); - } - return result; - } - - public Date getNotBefore() { - return new Date(getNotBeforeInternal()); - } - - private long getNotBeforeInternal() { - long result = notBefore; - if (result == -1) { - notBefore = result = tbsCert.getValidity().getNotBefore().getTime(); - } - return result; - } - - public Date getNotAfter() { - return new Date(getNotAfterInternal()); - } - - private long getNotAfterInternal() { - long result = notAfter; - if (result == -1) { - notAfter = result = tbsCert.getValidity().getNotAfter().getTime(); - } - return result; - } - - public byte[] getTBSCertificate() throws CertificateEncodingException { - return getTbsCertificateInternal().clone(); - } - - private byte[] getTbsCertificateInternal() { - byte[] result = tbsCertificate; - if (result == null) { - tbsCertificate = result = tbsCert.getEncoded(); - } - return result; - } - - public byte[] getSignature() { - return getSignatureInternal().clone(); - } - - private byte[] getSignatureInternal() { - byte[] result = signature; - if (result == null) { - signature = result = certificate.getSignatureValue(); - } - return result; - } - - public String getSigAlgName() { - String result = sigAlgName; - if (result == null) { - String sigAlgOIDLocal = getSigAlgOID(); - // retrieve the name of the signing algorithm - result = AlgNameMapper.map2AlgName(sigAlgOIDLocal); - if (result == null) { - // if could not be found, use OID as a name - result = sigAlgOIDLocal; - } - sigAlgName = result; - } - return result; - } - - public String getSigAlgOID() { - String result = sigAlgOID; - if (result == null) { - // if info was not retrieved (and cached), do it: - sigAlgOID = result = tbsCert.getSignature().getAlgorithm(); - } - return result; - } - - public byte[] getSigAlgParams() { - if (nullSigAlgParams) { - return null; - } - byte[] result = sigAlgParams; - if (result == null) { - result = tbsCert.getSignature().getParameters(); - if (result == null) { - nullSigAlgParams = true; - return null; - } - sigAlgParams = result; - } - return result; - } - - public boolean[] getIssuerUniqueID() { - return tbsCert.getIssuerUniqueID(); - } - - public boolean[] getSubjectUniqueID() { - return tbsCert.getSubjectUniqueID(); - } - - public boolean[] getKeyUsage() { - if (extensions == null) { - return null; - } - return extensions.valueOfKeyUsage(); - } - - public List<String> getExtendedKeyUsage() - throws CertificateParsingException { - if (extensions == null) { - return null; - } - try { - return extensions.valueOfExtendedKeyUsage(); - } catch (IOException e) { - throw new CertificateParsingException(e); - } - } - - public int getBasicConstraints() { - if (extensions == null) { - return -1; - } - return extensions.valueOfBasicConstraints(); - } - - public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException { - if (extensions == null) { - return null; - } - try { - // Retrieve the extension value from the cached extensions object - // This extension is not checked for correctness during - // certificate generation, so now it can throw exception - return extensions.valueOfSubjectAlternativeName(); - } catch (IOException e) { - throw new CertificateParsingException(e); - } - } - - /** - * @see java.security.cert.X509Certificate#getIssuerAlternativeNames() - * method documentation for more information. - */ - public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException { - if (extensions == null) { - return null; - } - try { - // Retrieve the extension value from the cached extensions object - // This extension is not checked for correctness during - // certificate generation, so now it can throw exception - return extensions.valueOfIssuerAlternativeName(); - } catch (IOException e) { - throw new CertificateParsingException(e); - } - } - - @Override public byte[] getEncoded() throws CertificateEncodingException { - return getEncodedInternal().clone(); - } - private byte[] getEncodedInternal() throws CertificateEncodingException { - byte[] result = encoding; - if (encoding == null) { - encoding = result = certificate.getEncoded(); - } - return result; - } - - @Override public PublicKey getPublicKey() { - PublicKey result = publicKey; - if (result == null) { - publicKey = result = tbsCert.getSubjectPublicKeyInfo().getPublicKey(); - } - return result; - } - - @Override public String toString() { - return certificate.toString(); - } - - @Override public void verify(PublicKey key) - throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, - NoSuchProviderException, SignatureException { - - Signature signature = Signature.getInstance(getSigAlgName()); - signature.initVerify(key); - // retrieve the encoding of the TBSCertificate structure - byte[] tbsCertificateLocal = getTbsCertificateInternal(); - // compute and verify the signature - signature.update(tbsCertificateLocal, 0, tbsCertificateLocal.length); - if (!signature.verify(certificate.getSignatureValue())) { - throw new SignatureException("Signature was not verified"); - } - } - - @Override public void verify(PublicKey key, String sigProvider) - throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, - NoSuchProviderException, SignatureException { - - Signature signature = Signature.getInstance(getSigAlgName(), sigProvider); - signature.initVerify(key); - // retrieve the encoding of the TBSCertificate structure - byte[] tbsCertificateLocal = getTbsCertificateInternal(); - // compute and verify the signature - signature.update(tbsCertificateLocal, 0, tbsCertificateLocal.length); - if (!signature.verify(certificate.getSignatureValue())) { - throw new SignatureException("Signature was not verified"); - } - } - - @Override public Set<String> getNonCriticalExtensionOIDs() { - if (extensions == null) { - return null; - } - // retrieve the info from the cached extensions object - return extensions.getNonCriticalExtensions(); - } - - @Override public Set<String> getCriticalExtensionOIDs() { - if (extensions == null) { - return null; - } - // retrieve the info from the cached extensions object - return extensions.getCriticalExtensions(); - } - - @Override public byte[] getExtensionValue(String oid) { - if (extensions == null) { - return null; - } - // retrieve the info from the cached extensions object - Extension ext = extensions.getExtensionByOID(oid); - return (ext == null) ? null : ext.getRawExtnValue(); - } - - @Override public boolean hasUnsupportedCriticalExtension() { - if (extensions == null) { - return false; - } - // retrieve the info from the cached extensions object - return extensions.hasUnsupportedCritical(); - } - -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java deleted file mode 100644 index 3699700..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertPathImpl.java +++ /dev/null @@ -1,451 +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. - */ - -/** -* @author Alexander Y. Kleymenov -* @version $Revision$ -*/ - -package org.apache.harmony.security.provider.cert; - -import java.io.IOException; -import java.io.InputStream; -import java.security.cert.CertPath; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import org.apache.harmony.security.asn1.ASN1Any; -import org.apache.harmony.security.asn1.ASN1Explicit; -import org.apache.harmony.security.asn1.ASN1Implicit; -import org.apache.harmony.security.asn1.ASN1Oid; -import org.apache.harmony.security.asn1.ASN1Sequence; -import org.apache.harmony.security.asn1.ASN1SequenceOf; -import org.apache.harmony.security.asn1.ASN1Type; -import org.apache.harmony.security.asn1.BerInputStream; -import org.apache.harmony.security.pkcs7.ContentInfo; -import org.apache.harmony.security.pkcs7.SignedData; -import org.apache.harmony.security.x509.Certificate; - -/** - * This class is an implementation of X.509 CertPath. This implementation - * provides ability to create the instance of X.509 Certification Path - * by several means:<br> - * - * 1. It can be created over the list of X.509 certificates - * (implementations of X509Certificate class) provided in constructor.<br> - * - * 2. It can be created by means of <code>getInstance</code> methods - * on the base of the following ASN.1 DER encoded forms:<br> - * - * - PkiPath as defined in - * ITU-T Recommendation X.509(2000) Corrigendum 1(2001) - * (can be seen at - * ftp://ftp.bull.com/pub/OSIdirectory/DefectResolution/TechnicalCorrigenda/ApprovedTechnicalCorrigendaToX.509/8%7CX.509-TC1(4th).pdf) - * <br> - * - PKCS #7 SignedData object provided in the form of - * ContentInfo structure. CertPath object is generated on the base of - * certificates presented in <code>certificates</code> field of the SignedData - * object which in its turn is retrieved from ContentInfo structure. - * (see http://www.ietf.org/rfc/rfc2315.txt - * for more info on PKCS #7) - * <br> - * - */ -public class X509CertPathImpl extends CertPath { - /** - * @serial - */ - private static final long serialVersionUID = 7989755106209515436L; - - /** - * Supported encoding types for CerthPath. Used by the various APIs that - * encode this into bytes such as {@link #getEncoded()}. - */ - private enum Encoding { - PKI_PATH("PkiPath"), - PKCS7("PKCS7"); - - private final String apiName; - - Encoding(String apiName) { - this.apiName = apiName; - } - - static Encoding findByApiName(String apiName) throws CertificateEncodingException { - for (Encoding element : values()) { - if (element.apiName.equals(apiName)) { - return element; - } - } - - return null; - } - } - - /** Unmodifiable list of encodings for the API. */ - static final List<String> encodings = Collections.unmodifiableList(Arrays.asList(new String[] { - Encoding.PKI_PATH.apiName, - Encoding.PKCS7.apiName, - })); - - /** The list of certificates in the order of target toward trust anchor. */ - private final List<X509Certificate> certificates; - - /** PkiPath encoding of the certification path. */ - private byte[] pkiPathEncoding; - - /** PKCS7 encoding of the certification path. */ - private byte[] pkcs7Encoding; - - /** - * Creates an instance of X.509 CertPath over the specified list of - * certificates. - * - * @throws CertificateException if some of the object in the list is not an - * instance of subclass of X509Certificate. - */ - public X509CertPathImpl(List<? extends java.security.cert.Certificate> certs) - throws CertificateException { - super("X.509"); - - final int size = certs.size(); - certificates = new ArrayList<X509Certificate>(size); - - for (int i = 0; i < size; i++) { - final java.security.cert.Certificate cert = certs.get(i); - if (!(cert instanceof X509Certificate)) { - throw new CertificateException("Certificate " + i + " is not an X.509 certificate"); - } - - certificates.add((X509Certificate) cert); - } - } - - /** - * Creates an X.509 CertPath over the specified {@code certs}. The - * {@code certs} should be sorted correctly when calling into the - * constructor. Additionally, the {@code encodedPath} should match the - * expected output for the {@code type} of encoding. - */ - private X509CertPathImpl(List<X509Certificate> certs, Encoding type) { - super("X.509"); - - certificates = certs; - } - - /** - * Extract a CertPath from a PKCS#7 {@code contentInfo} object. - */ - private static X509CertPathImpl getCertPathFromContentInfo(ContentInfo contentInfo) - throws CertificateException { - final SignedData sd = contentInfo.getSignedData(); - if (sd == null) { - throw new CertificateException("Incorrect PKCS7 encoded form: missing signed data"); - } - - List<Certificate> certs = sd.getCertificates(); - if (certs == null) { - certs = Collections.emptyList(); - } - - final List<X509Certificate> result = new ArrayList<X509Certificate>(certs.size()); - for (Certificate cert : certs) { - result.add(new X509CertImpl(cert)); - } - - return new X509CertPathImpl(result, Encoding.PKCS7); - } - - /** - * Generates certification path object on the base of PkiPath encoded form - * provided via input stream. - * - * @throws CertificateException if some problems occurred during the - * decoding. - */ - public static X509CertPathImpl getInstance(InputStream in) throws CertificateException { - try { - return (X509CertPathImpl) ASN1.decode(in); - } catch (IOException e) { - throw new CertificateException("Failed to decode CertPath", e); - } - } - - /** - * Generates certification path object on the basis of encoding provided via - * input stream. The format of provided encoded form is specified by - * parameter <code>encoding</code>. - * - * @throws CertificateException if specified encoding form is not supported, - * or some problems occurred during the decoding. - */ - public static X509CertPathImpl getInstance(InputStream in, String encoding) - throws CertificateException { - try { - final Encoding encType = Encoding.findByApiName(encoding); - if (encType == null) { - throw new CertificateException("Unsupported encoding: " + encoding); - } - - switch (encType) { - case PKI_PATH: - return (X509CertPathImpl) ASN1.decode(in); - case PKCS7: - return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in)); - default: - throw new CertificateException("Unsupported encoding: " + encoding); - } - } catch (IOException e) { - throw new CertificateException("Failed to decode CertPath", e); - } - } - - /** - * Generates certification path object on the base of PkiPath - * encoded form provided via array of bytes. - * @throws CertificateException if some problems occurred during - * the decoding. - */ - public static X509CertPathImpl getInstance(byte[] in) throws CertificateException { - try { - return (X509CertPathImpl) ASN1.decode(in); - } catch (IOException e) { - throw new CertificateException("Failed to decode CertPath", e); - } - } - - /** - * Generates certification path object on the base of encoding provided via - * array of bytes. The format of provided encoded form is specified by - * parameter {@code encoding}. - * - * @throws CertificateException if specified encoding form is not supported, - * or some problems occurred during the decoding. - */ - public static X509CertPathImpl getInstance(byte[] in, String encoding) - throws CertificateException { - try { - final Encoding encType = Encoding.findByApiName(encoding); - if (encType == null) { - throw new CertificateException("Unsupported encoding: " + encoding); - } - - switch (encType) { - case PKI_PATH: - return (X509CertPathImpl) ASN1.decode(in); - case PKCS7: - return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in)); - default: - throw new CertificateException("Unsupported encoding: " + encoding); - } - } catch (IOException e) { - throw new CertificateException("Failed to decode CertPath", e); - } - } - - // --------------------------------------------------------------------- - // ---- java.security.cert.CertPath abstract method implementations ---- - // --------------------------------------------------------------------- - - /** - * @see java.security.cert.CertPath#getCertificates() - * method documentation for more info - */ - @Override - public List<X509Certificate> getCertificates() { - return Collections.unmodifiableList(certificates); - } - - /** - * Returns in PkiPath format which is our default encoding. - * - * @see java.security.cert.CertPath#getEncoded() - */ - @Override - public byte[] getEncoded() throws CertificateEncodingException { - return getEncoded(Encoding.PKI_PATH); - } - - /** - * @see #getEncoded(String) - */ - private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException { - switch (encoding) { - case PKI_PATH: - if (pkiPathEncoding == null) { - pkiPathEncoding = ASN1.encode(this); - } - - return pkiPathEncoding.clone(); - case PKCS7: - if (pkcs7Encoding == null) { - pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this); - } - - return pkcs7Encoding.clone(); - default: - throw new CertificateEncodingException("Unsupported encoding: " + encoding); - } - } - - /** - * @see java.security.cert.CertPath#getEncoded(String) - */ - @Override - public byte[] getEncoded(String encoding) throws CertificateEncodingException { - final Encoding encType = Encoding.findByApiName(encoding); - if (encType == null) { - throw new CertificateEncodingException("Unsupported encoding: " + encoding); - } - - return getEncoded(encType); - } - - /** - * @see java.security.cert.CertPath#getEncodings() - * method documentation for more info - */ - @Override - public Iterator<String> getEncodings() { - return encodings.iterator(); - } - - /** - * ASN.1 DER Encoder/Decoder for PkiPath structure. - */ - public static final ASN1SequenceOf ASN1 = new ASN1SequenceOf(ASN1Any.getInstance()) { - /** - * Builds the instance of X509CertPathImpl on the base of the list of - * ASN.1 encodings of X.509 certificates provided via PkiPath structure. - * This method participates in decoding process. - */ - public Object getDecodedObject(BerInputStream in) throws IOException { - // retrieve the decoded content - final List<byte[]> encodedCerts = (List<byte[]>) in.content; - - final int size = encodedCerts.size(); - final List<X509Certificate> certificates = new ArrayList<X509Certificate>(size); - - for (int i = size - 1; i >= 0; i--) { - // create the X.509 certificate on the base of its encoded form - // and add it to the list. - certificates.add(new X509CertImpl((Certificate) Certificate.ASN1 - .decode(encodedCerts.get(i)))); - } - - // create and return the resulting object - return new X509CertPathImpl(certificates, Encoding.PKI_PATH); - } - - /** - * Returns the Collection of the encoded form of certificates contained - * in the X509CertPathImpl object to be encoded. - * This method participates in encoding process. - */ - public Collection<byte[]> getValues(Object object) { - // object to be encoded - final X509CertPathImpl cp = (X509CertPathImpl) object; - - // if it has no certificates in it - create the sequence of size 0 - if (cp.certificates == null) { - return Collections.emptyList(); - } - - final int size = cp.certificates.size(); - final List<byte[]> encodings = new ArrayList<byte[]>(size); - - try { - for (int i = size - 1; i >= 0; i--) { - // get the encoded form of certificate and place it into the - // list to be encoded in PkiPath format - encodings.add(cp.certificates.get(i).getEncoded()); - } - } catch (CertificateEncodingException e) { - throw new IllegalArgumentException("Encoding error occurred", e); - } - - return encodings; - } - }; - - - /** - * Encoder for PKCS#7 SignedData. It is assumed that only certificate field - * is important all other fields contain pre-calculated encodings. - */ - private static final ASN1Sequence ASN1_SIGNED_DATA = new ASN1Sequence( - new ASN1Type[] { - // version ,digestAlgorithms, content info - ASN1Any.getInstance(), - // certificates - new ASN1Implicit(0, ASN1), - // set of crls is optional and is missed here - ASN1Any.getInstance(),// signers info - }) { - - // precalculated ASN.1 encodings for - // version ,digestAlgorithms, content info field of SignedData - private final byte[] PRECALCULATED_HEAD = new byte[] { 0x02, 0x01, - 0x01,// version (v1) - 0x31, 0x00,// empty set of DigestAlgorithms - 0x30, 0x03, 0x06, 0x01, 0x00 // empty ContentInfo with oid=0 - }; - - // precalculated empty set of SignerInfos - private final byte[] SIGNERS_INFO = new byte[] { 0x31, 0x00 }; - - protected void getValues(Object object, Object[] values) { - values[0] = PRECALCULATED_HEAD; - values[1] = object; // pass X509CertPathImpl object - values[2] = SIGNERS_INFO; - } - - // stub to prevent using the instance as decoder - public Object decode(BerInputStream in) throws IOException { - throw new RuntimeException( - "Invalid use of encoder for PKCS#7 SignedData object"); - } - }; - - private static final ASN1Sequence PKCS7_SIGNED_DATA_OBJECT = new ASN1Sequence( - new ASN1Type[] { ASN1Any.getInstance(), // contentType - new ASN1Explicit(0, ASN1_SIGNED_DATA) // SignedData - }) { - - // precalculated ASN.1 encoding for SignedData object oid - private final byte[] SIGNED_DATA_OID = ASN1Oid.getInstance().encode( - ContentInfo.SIGNED_DATA); - - protected void getValues(Object object, Object[] values) { - values[0] = SIGNED_DATA_OID; - values[1] = object; // pass X509CertPathImpl object - } - - // stub to prevent using the instance as decoder - public Object decode(BerInputStream in) throws IOException { - throw new RuntimeException( - "Invalid use of encoder for PKCS#7 SignedData object"); - } - }; -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java index 7c2785a..ad5ac7d 100644 --- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java +++ b/luni/src/main/java/org/apache/harmony/security/provider/crypto/CryptoProvider.java @@ -20,12 +20,9 @@ package org.apache.harmony.security.provider.crypto; import java.security.Provider; /** - * Implementation of Provider for SecureRandom, MessageDigest and Signature - * using a Secure Hash Algorithm, SHA-1; - * see SECURE HASH STANDARD, FIPS PUB 180-1 (http://www.itl.nist.gov/fipspubs/fip180-1.htm) <BR> - * <BR> - * The implementation supports "SHA1PRNG", "SHA-1" and "SHA1withDSA" algorithms described in - * JavaTM Cryptography Architecture, API Specification & Reference + * Implementation of Provider for SecureRandom. The implementation supports the + * "SHA1PRNG" algorithm described in JavaTM Cryptography Architecture, API + * Specification & Reference */ public final class CryptoProvider extends Provider { @@ -36,46 +33,10 @@ public final class CryptoProvider extends Provider { * Creates a Provider and puts parameters */ public CryptoProvider() { - super("Crypto", 1.0, "HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)"); - // names of classes implementing services - final String MD_NAME = "org.apache.harmony.security.provider.crypto.SHA1_MessageDigestImpl"; - final String SR_NAME = "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl"; - - final String SIGN_NAME = "org.apache.harmony.security.provider.crypto.SHA1withDSA_SignatureImpl"; - - final String SIGN_ALIAS = "SHA1withDSA"; - - - final String KEYF_NAME = "org.apache.harmony.security.provider.crypto.DSAKeyFactoryImpl"; - - put("MessageDigest.SHA-1", MD_NAME); - put("MessageDigest.SHA-1 ImplementedIn", "Software"); - put("Alg.Alias.MessageDigest.SHA1", "SHA-1"); - put("Alg.Alias.MessageDigest.SHA", "SHA-1"); - - put("SecureRandom.SHA1PRNG", SR_NAME); + put("SecureRandom.SHA1PRNG", + "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl"); put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); - - put("Signature.SHA1withDSA", SIGN_NAME); - put("Signature.SHA1withDSA ImplementedIn", "Software"); - put("Alg.Alias.Signature.SHAwithDSA", SIGN_ALIAS); - put("Alg.Alias.Signature.DSAwithSHA1", SIGN_ALIAS); - put("Alg.Alias.Signature.SHA1/DSA", SIGN_ALIAS); - put("Alg.Alias.Signature.SHA/DSA", SIGN_ALIAS); - put("Alg.Alias.Signature.SHA-1/DSA", SIGN_ALIAS); - put("Alg.Alias.Signature.DSA", SIGN_ALIAS); - put("Alg.Alias.Signature.DSS", SIGN_ALIAS); - - put("Alg.Alias.Signature.OID.1.2.840.10040.4.3", SIGN_ALIAS); - put("Alg.Alias.Signature.1.2.840.10040.4.3", SIGN_ALIAS); - put("Alg.Alias.Signature.1.3.14.3.2.13", SIGN_ALIAS); - put("Alg.Alias.Signature.1.3.14.3.2.27", SIGN_ALIAS); - - put("KeyFactory.DSA", KEYF_NAME); - put("KeyFactory.DSA ImplementedIn", "Software"); - put("Alg.Alias.KeyFactory.1.3.14.3.2.12", "DSA"); - put("Alg.Alias.KeyFactory.1.2.840.10040.4.1", "DSA"); } } diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAKeyFactoryImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAKeyFactoryImpl.java deleted file mode 100644 index 690d16e..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAKeyFactoryImpl.java +++ /dev/null @@ -1,217 +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 org.apache.harmony.security.provider.crypto; - -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyFactorySpi; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.DSAPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; - -public class DSAKeyFactoryImpl extends KeyFactorySpi { - - /** - * This method generates a DSAPrivateKey object from the provided key specification. - * - * @param - * keySpec - the specification (key material) for the DSAPrivateKey. - * - * @return - * a DSAPrivateKey object - * - * @throws InvalidKeySpecException - * if "keySpec" is neither DSAPrivateKeySpec nor PKCS8EncodedKeySpec - */ - protected PrivateKey engineGeneratePrivate(KeySpec keySpec) - throws InvalidKeySpecException { - - if (keySpec != null) { - if (keySpec instanceof DSAPrivateKeySpec) { - - return new DSAPrivateKeyImpl((DSAPrivateKeySpec) keySpec); - } - if (keySpec instanceof PKCS8EncodedKeySpec) { - - return new DSAPrivateKeyImpl((PKCS8EncodedKeySpec) keySpec); - } - } - throw new InvalidKeySpecException("'keySpec' is neither DSAPrivateKeySpec nor PKCS8EncodedKeySpec"); - } - - /** - * This method generates a DSAPublicKey object from the provided key specification. - * - * @param - * keySpec - the specification (key material) for the DSAPublicKey. - * - * @return - * a DSAPublicKey object - * - * @throws InvalidKeySpecException - * if "keySpec" is neither DSAPublicKeySpec nor X509EncodedKeySpec - */ - protected PublicKey engineGeneratePublic(KeySpec keySpec) - throws InvalidKeySpecException { - - if (keySpec != null) { - if (keySpec instanceof DSAPublicKeySpec) { - - return new DSAPublicKeyImpl((DSAPublicKeySpec) keySpec); - } - if (keySpec instanceof X509EncodedKeySpec) { - - return new DSAPublicKeyImpl((X509EncodedKeySpec) keySpec); - } - } - throw new InvalidKeySpecException("'keySpec' is neither DSAPublicKeySpec nor X509EncodedKeySpec"); - } - - /** - * This method returns a specification for the supplied key. - * - * The specification will be returned in the form of an object of the type - * specified by keySpec. - * - * @param key - - * either DSAPrivateKey or DSAPublicKey - * @param keySpec - - * either DSAPrivateKeySpec.class or DSAPublicKeySpec.class - * - * @return either a DSAPrivateKeySpec or a DSAPublicKeySpec - * - * @throws InvalidKeySpecException - * if "keySpec" is not a specification for DSAPublicKey or - * DSAPrivateKey - */ - protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec) - throws InvalidKeySpecException { - - BigInteger p, q, g, x, y; - - if (key != null) { - if (keySpec == null) { - throw new NullPointerException("keySpec == null"); - } - if (key instanceof DSAPrivateKey) { - DSAPrivateKey privateKey = (DSAPrivateKey) key; - - if (keySpec.equals(DSAPrivateKeySpec.class)) { - - x = privateKey.getX(); - - DSAParams params = privateKey.getParams(); - - p = params.getP(); - q = params.getQ(); - g = params.getG(); - - return (T) (new DSAPrivateKeySpec(x, p, q, g)); - } - - if (keySpec.equals(PKCS8EncodedKeySpec.class)) { - return (T) (new PKCS8EncodedKeySpec(key.getEncoded())); - } - - throw new InvalidKeySpecException("'keySpec' is neither DSAPrivateKeySpec nor PKCS8EncodedKeySpec"); - } - - if (key instanceof DSAPublicKey) { - DSAPublicKey publicKey = (DSAPublicKey) key; - - if (keySpec.equals(DSAPublicKeySpec.class)) { - - y = publicKey.getY(); - - DSAParams params = publicKey.getParams(); - - p = params.getP(); - q = params.getQ(); - g = params.getG(); - - return (T) (new DSAPublicKeySpec(y, p, q, g)); - } - - if (keySpec.equals(X509EncodedKeySpec.class)) { - return (T) (new X509EncodedKeySpec(key.getEncoded())); - } - - throw new InvalidKeySpecException("'keySpec' is neither DSAPublicKeySpec nor X509EncodedKeySpec"); - } - } - throw new InvalidKeySpecException("'key' is neither DSAPublicKey nor DSAPrivateKey"); - } - - /** - * The method generates a DSAPublicKey object from the provided key. - * - * @param - * key - a DSAPublicKey object or DSAPrivateKey object. - * - * @return - * object of the same type as the "key" argument - * - * @throws InvalidKeyException - * if "key" is neither DSAPublicKey nor DSAPrivateKey - */ - protected Key engineTranslateKey(Key key) throws InvalidKeyException { - - if (key != null) { - if (key instanceof DSAPrivateKey) { - - DSAPrivateKey privateKey = (DSAPrivateKey) key; - DSAParams params = privateKey.getParams(); - - try { - return engineGeneratePrivate(new DSAPrivateKeySpec( - privateKey.getX(), params.getP(), params.getQ(), - params.getG())); - } catch (InvalidKeySpecException e) { - // Actually this exception shouldn't be thrown - throw new InvalidKeyException("ATTENTION: InvalidKeySpecException: " + e); - } - } - - if (key instanceof DSAPublicKey) { - - DSAPublicKey publicKey = (DSAPublicKey) key; - DSAParams params = publicKey.getParams(); - - try { - return engineGeneratePublic(new DSAPublicKeySpec(publicKey - .getY(), params.getP(), params.getQ(), params - .getG())); - } catch (InvalidKeySpecException e) { - // Actually this exception shouldn't be thrown - throw new InvalidKeyException("ATTENTION: InvalidKeySpecException: " + e); - } - } - } - throw new InvalidKeyException("'key' is neither DSAPublicKey nor DSAPrivateKey"); - } - -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPrivateKeyImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPrivateKeyImpl.java deleted file mode 100644 index c0fc766..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPrivateKeyImpl.java +++ /dev/null @@ -1,159 +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. - */ - - /* - * TODO - * 1. The class extends the PrivateKeyImpl class in "org.apache.harmony.security" package. - * - * 2. See a compatibility with RI comments - * in the below "DSAPrivateKeyImpl(PKCS8EncodedKeySpec keySpec)" constructor. - */ - - -package org.apache.harmony.security.provider.crypto; - -import java.io.IOException; -import java.io.NotActiveException; -import java.math.BigInteger; -import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPrivateKey; -import java.security.spec.DSAParameterSpec; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import org.apache.harmony.security.PrivateKeyImpl; -import org.apache.harmony.security.asn1.ASN1Integer; -import org.apache.harmony.security.pkcs8.PrivateKeyInfo; -import org.apache.harmony.security.utils.AlgNameMapper; -import org.apache.harmony.security.x509.AlgorithmIdentifier; - -/** - * The class provides DSAPrivateKey functionality by extending a class implementing PrivateKey - * and implementing methods defined in both interfaces, DSAKey and DSAPrivateKey - */ -public class DSAPrivateKeyImpl extends PrivateKeyImpl implements DSAPrivateKey { - - /** - * @serial - */ - private static final long serialVersionUID = -4716227614104950081L; - - private BigInteger x, g, p, q; - - private transient DSAParams params; - - /** - * Creates object from DSAPrivateKeySpec. - * - * @param keySpec - a DSAPrivateKeySpec object - */ - public DSAPrivateKeyImpl(DSAPrivateKeySpec keySpec) { - - super("DSA"); - - PrivateKeyInfo pki; - - g = keySpec.getG(); - p = keySpec.getP(); - q = keySpec.getQ(); - - ThreeIntegerSequence threeInts = new ThreeIntegerSequence(p - .toByteArray(), q.toByteArray(), g.toByteArray()); - - AlgorithmIdentifier ai = new AlgorithmIdentifier(AlgNameMapper - .map2OID("DSA"), - threeInts.getEncoded()); - x = keySpec.getX(); - - pki = new PrivateKeyInfo(0, ai, ASN1Integer.getInstance().encode( - x.toByteArray()), null); - - setEncoding(pki.getEncoded()); - - params = new DSAParameterSpec(p, q, g); - } - - /** - * Creates object from PKCS8EncodedKeySpec. - * - * @param keySpec - a XPKCS8EncodedKeySpec object - * - * @throws InvalidKeySpecException - if key data cannot be obtain from encoded format - */ - public DSAPrivateKeyImpl(PKCS8EncodedKeySpec keySpec) - throws InvalidKeySpecException { - - super("DSA"); - - AlgorithmIdentifier ai; - ThreeIntegerSequence threeInts = null; - - String alg, algName; - - byte[] encoding = keySpec.getEncoded(); - - PrivateKeyInfo privateKeyInfo = null; - - try { - privateKeyInfo = (PrivateKeyInfo) PrivateKeyInfo.ASN1 - .decode(encoding); - } catch (IOException e) { - throw new InvalidKeySpecException("Failed to decode keySpec encoding: " + e); - } - - try { - x = new BigInteger((byte[]) ASN1Integer.getInstance().decode( - privateKeyInfo.getPrivateKey())); - } catch (IOException e) { - throw new InvalidKeySpecException("Failed to decode parameters: " + e); - } - - ai = privateKeyInfo.getAlgorithmIdentifier(); - try { - threeInts = (ThreeIntegerSequence) ThreeIntegerSequence.ASN1 - .decode(ai.getParameters()); - } catch (IOException e) { - throw new InvalidKeySpecException("Failed to decode parameters: " + e); - } - p = new BigInteger(threeInts.p); - q = new BigInteger(threeInts.q); - g = new BigInteger(threeInts.g); - params = new DSAParameterSpec(p, q, g); - setEncoding(encoding); - - /* - * the following code implements RI behavior - */ - alg = ai.getAlgorithm(); - algName = AlgNameMapper.map2AlgName(alg); - setAlgorithm(algName == null ? alg : algName); - } - - public BigInteger getX() { - return x; - } - - public DSAParams getParams() { - return params; - } - - private void readObject(java.io.ObjectInputStream in) throws NotActiveException, IOException, ClassNotFoundException { - in.defaultReadObject(); - params = new DSAParameterSpec(p, q, g); - } - -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPublicKeyImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPublicKeyImpl.java deleted file mode 100644 index 6b35970..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/DSAPublicKeyImpl.java +++ /dev/null @@ -1,171 +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. - */ - - /* - * TODO - * 1. The class extends the PublicKeyImpl class in "org.apache.harmony.security" package. - * - * 2. The class uses methods in the auxiliary non-public "ThreeIntegerSequence" class - * defined along with the "DSAPrivateKeyImpl" class. - * - * 3. See a compatibility with RI comments - * in the below "DSAPublicKeyImpl(X509EncodedKeySpec keySpec)" constructor. - */ - -package org.apache.harmony.security.provider.crypto; - -import java.io.IOException; -import java.io.NotActiveException; -import java.math.BigInteger; -import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPublicKey; -import java.security.spec.DSAParameterSpec; -import java.security.spec.DSAPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -import org.apache.harmony.security.PublicKeyImpl; -import org.apache.harmony.security.asn1.ASN1Integer; -import org.apache.harmony.security.utils.AlgNameMapper; -import org.apache.harmony.security.x509.AlgorithmIdentifier; -import org.apache.harmony.security.x509.SubjectPublicKeyInfo; - -/** - * The class provides DSAPublicKey functionality by extending a class implementing PublicKey - * and implementing methods defined in both interfaces, DSAKey and DSAPublicKey - */ -public class DSAPublicKeyImpl extends PublicKeyImpl implements DSAPublicKey { - - /** - * @serial - */ - private static final long serialVersionUID = -2279672131310978336L; - - private BigInteger y, g, p, q; - - private transient DSAParams params; - - /** - * Creates object from DSAPublicKeySpec. - * - * @param keySpec - a DSAPublicKeySpec object - */ - public DSAPublicKeyImpl(DSAPublicKeySpec keySpec) { - - super("DSA"); - - SubjectPublicKeyInfo spki; - - p = keySpec.getP(); - q = keySpec.getQ(); - g = keySpec.getG(); - - ThreeIntegerSequence threeInts = new ThreeIntegerSequence(p - .toByteArray(), q.toByteArray(), g.toByteArray()); - - AlgorithmIdentifier ai = new AlgorithmIdentifier(AlgNameMapper - .map2OID("DSA"), - threeInts.getEncoded()); - - y = keySpec.getY(); - - spki = new SubjectPublicKeyInfo(ai, ASN1Integer.getInstance().encode( - y.toByteArray())); - setEncoding(spki.getEncoded()); - - params = (DSAParams) (new DSAParameterSpec(p, q, g)); - } - - /** - * Creates object from X509EncodedKeySpec. - * - * @param keySpec - a X509EncodedKeySpec object - * - * @throws InvalidKeySpecException - if key data cannot be obtain from encoded format - */ - public DSAPublicKeyImpl(X509EncodedKeySpec keySpec) - throws InvalidKeySpecException { - - super("DSA"); - - AlgorithmIdentifier ai; - ThreeIntegerSequence threeInts = null; - - SubjectPublicKeyInfo subjectPublicKeyInfo = null; - - byte[] encoding = keySpec.getEncoded(); - - String alg, algName; - - try { - subjectPublicKeyInfo = (SubjectPublicKeyInfo) SubjectPublicKeyInfo.ASN1 - .decode(encoding); - } catch (IOException e) { - throw new InvalidKeySpecException("Failed to decode keySpec encoding: " + e); - } - - try { - y = new BigInteger((byte[]) ASN1Integer.getInstance().decode( - subjectPublicKeyInfo.getSubjectPublicKey())); - } catch (IOException e) { - throw new InvalidKeySpecException("Failed to decode parameters: " + e); - } - - ai = subjectPublicKeyInfo.getAlgorithmIdentifier(); - - try { - threeInts = (ThreeIntegerSequence) ThreeIntegerSequence.ASN1 - .decode(ai.getParameters()); - } catch (IOException e) { - throw new InvalidKeySpecException("Failed to decode parameters: " + e); - } - p = new BigInteger(threeInts.p); - q = new BigInteger(threeInts.q); - g = new BigInteger(threeInts.g); - params = (DSAParams) (new DSAParameterSpec(p, q, g)); - - setEncoding(encoding); - - /* - * the following code implements RI behavior - */ - alg = ai.getAlgorithm(); - algName = AlgNameMapper.map2AlgName(alg); - setAlgorithm(algName == null ? alg : algName); - } - - /** - * @return - * a value of a public key (y). - */ - public BigInteger getY() { - return y; - } - - /** - * @return - * DSA key parameters (p, q, g). - */ - public DSAParams getParams() { - return params; - } - - private void readObject(java.io.ObjectInputStream in) throws NotActiveException, IOException, ClassNotFoundException { - in.defaultReadObject(); - params = new DSAParameterSpec(p, q, g); - } - -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1_MessageDigestImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1_MessageDigestImpl.java deleted file mode 100644 index 3f41f18..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1_MessageDigestImpl.java +++ /dev/null @@ -1,306 +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 org.apache.harmony.security.provider.crypto; - -import java.security.DigestException; -import java.security.MessageDigestSpi; -import java.util.Arrays; - -import static org.apache.harmony.security.provider.crypto.SHA1Constants.*; - -/** - * This class extends the MessageDigestSpi class implementing all its abstract methods; - * it overrides the "Object clone()" and "int engineGetDigestLength()" methods. <BR> - * The class implements the Cloneable interface. - */ -public class SHA1_MessageDigestImpl extends MessageDigestSpi implements Cloneable { - private int[] buffer; // buffer has the following structure: - // - 0-16 - frame for accumulating a message - // - 17-79 - for SHA1Impl methods - // - 80 - unused - // - 81 - to store length of the message - // - 82-86 - frame for current message digest - - private byte[] oneByte; // one byte buffer needed to use in engineUpdate(byte) - // having buffer as private field is just optimization - - private long messageLength; // total length of bytes supplied by user - - - /** - * The constructor creates needed buffers and sets the engine at initial state - */ - public SHA1_MessageDigestImpl() { - - // BYTES_OFFSET +6 is minimal length required by methods in SHA1Impl - buffer = new int[BYTES_OFFSET +6]; - - oneByte = new byte[1]; - - engineReset(); - } - - - /** - * The method performs final actions and invokes the "computeHash(int[])" method. - * In case if there is no enough words in current frame - * after processing its data, extra frame is prepared and - * the "computeHash(int[])" method is invoked second time. <BR> - * - * After processing, the method resets engine's state - * - * @param - * digest - byte array - * @param - * offset - offset in digest - */ - private void processDigest(byte[] digest, int offset) { - - int i, j; // implementation variables - int lastWord; // - - long nBits = messageLength <<3 ; // length has to be calculated before padding - - engineUpdate( (byte) 0x80 ); // beginning byte in padding - - i = 0; // i contains number of beginning word for following loop - - lastWord = (buffer[BYTES_OFFSET] + 3)>>2 ; // computing of # of full words by shifting - // # of bytes - - // possible cases: - // - // - buffer[BYTES_OFFSET] == 0 - buffer frame is empty, - // padding byte was 64th in previous frame - // current frame should contain only message's length - // - // - lastWord < 14 - two last, these are 14 & 15, words in 16 word frame are free; - // no extra frame needed - // - lastWord = 14 - only one last, namely 15-th, word in frame doesn't contain bytes; - // extra frame is needed - // - lastWord > 14 - last word in frame is not full; - // extra frame is needed - - if ( buffer[BYTES_OFFSET] != 0 ) { - - if ( lastWord < 15 ) { - i = lastWord; - } else { - if ( lastWord == 15 ) { - buffer[15] = 0; // last word in frame is set to "0" - } - SHA1Impl.computeHash(buffer); - i = 0; - } - } - Arrays.fill(buffer, i, 14, 0); - - buffer[14] = (int)( nBits >>>32 ); - buffer[15] = (int)( nBits & 0xFFFFFFFF ); - SHA1Impl.computeHash(buffer); - - // converting 5-word frame into 20 bytes - j = offset; - for ( i = HASH_OFFSET; i < HASH_OFFSET +5; i++ ) { - int k = buffer[i]; - digest[j ] = (byte) ( k >>>24 ); // getting first byte from left - digest[j+1] = (byte) ( k >>>16 ); // getting second byte from left - digest[j+2] = (byte) ( k >>> 8 ); // getting third byte from left - digest[j+3] = (byte) ( k ); // getting fourth byte from left - j += 4; - } - - engineReset(); - } - - // methods specified in java.security.MessageDigestSpi - - /** - * Returns a "deep" copy of this SHA1MDImpl object. <BR> - * - * The method overrides "clone()" in class Object. <BR> - * - * @return - * a clone of this object - */ - public Object clone() throws CloneNotSupportedException { - SHA1_MessageDigestImpl cloneObj = (SHA1_MessageDigestImpl) super.clone(); - cloneObj.buffer = buffer.clone(); - cloneObj.oneByte = oneByte.clone(); - return cloneObj; - } - - - /** - * Computes a message digest value. <BR> - * - * The method resets the engine. <BR> - * - * The method overrides "engineDigest()" in class MessageDigestSpi. <BR> - * - * @return - * byte array containing message digest value - */ - protected byte[] engineDigest() { - byte[] hash = new byte[DIGEST_LENGTH]; - processDigest(hash, 0); - return hash; - } - - - /** - * Computes message digest value. - * Upon return, the value is stored in "buf" buffer beginning "offset" byte. <BR> - * - * The method resets the engine. <BR> - * - * The method overrides "engineDigest(byte[],int,int) in class MessageDigestSpi. - * - * @param - * buf byte array to store a message digest returned - * @param - * offset a position in the array for first byte of the message digest - * @param - * len number of bytes within buffer allotted for the message digest; - * as this implementation doesn't provide partial digests, - * len should be >= 20, DigestException is thrown otherwise - * @return - * the length of the message digest stored in the "buf" buffer; - * in this implementation the length=20 - * - * @throws IllegalArgumentException - * if null is passed to the "buf" argument <BR> - * if offset + len > buf.length <BR> - * if offset > buf.length or len > buf.length - * - * @throws DigestException - * if len < 20 - * - * @throws ArrayIndexOutOfBoundsException - * if offset < 0 - */ - protected int engineDigest(byte[] buf, int offset, int len) throws DigestException { - if (buf == null) { - throw new IllegalArgumentException("buf == null"); - } - if (offset > buf.length || len > buf.length || (len + offset) > buf.length) { - throw new IllegalArgumentException(); - } - if (len < DIGEST_LENGTH) { - throw new DigestException("len < DIGEST_LENGTH"); - } - if (offset < 0) { - throw new ArrayIndexOutOfBoundsException(offset); - } - - processDigest(buf, offset); - - return DIGEST_LENGTH; - } - - - /** - * Returns a message digest length. <BR> - * - * The method overrides "engineGetDigestLength()" in class MessageDigestSpi. <BR> - * - * @return - * total length of current message digest as an int value - */ - protected int engineGetDigestLength() { - return DIGEST_LENGTH; - } - - - /** - * Resets the engine. <BR> - * - * The method overrides "engineReset()" in class MessageDigestSpi. <BR> - */ - protected void engineReset() { - - messageLength = 0; - - buffer[BYTES_OFFSET] = 0; - buffer[HASH_OFFSET ] = H0; - buffer[HASH_OFFSET +1] = H1; - buffer[HASH_OFFSET +2] = H2; - buffer[HASH_OFFSET +3] = H3; - buffer[HASH_OFFSET +4] = H4; - } - - - /** - * Supplements a byte to current message. <BR> - * - * The method overrides "engineUpdate(byte)" in class MessageDigestSpi. <BR> - * - * @param - * input byte to add to current message - */ - protected void engineUpdate(byte input) { - - oneByte[0] = input; - SHA1Impl.updateHash( buffer, oneByte, 0, 0 ); - messageLength++; - } - - - /** - * Updates current message. <BR> - * - * The method overrides "engineUpdate(byte[],int,int)" in class MessageDigestSpi. <BR> - * - * The method silently returns if "len" <= 0. - * - * @param - * input a byte array - * @param - * offset a number of first byte in the "input" array to use for updating - * @param - * len a number of bytes to use - * - * @throws NullPointerException - * if null is passed to the "buf" argument - * - * @throws IllegalArgumentException - * if offset > buf.length or len > buf.length or - * (len + offset) > buf.length - * @throws ArrayIndexOutOfBoundsException - * offset < 0 - */ - protected void engineUpdate(byte[] input, int offset, int len) { - if (input == null) { - throw new IllegalArgumentException("input == null"); - } - if (len <= 0) { - return; - } - if (offset < 0) { - throw new ArrayIndexOutOfBoundsException(offset); - } - if (offset > input.length || len > input.length || (len + offset) > input.length) { - throw new IllegalArgumentException(); - } - - SHA1Impl.updateHash(buffer, input, offset, offset + len -1 ); - messageLength += len; - } - -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1withDSA_SignatureImpl.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1withDSA_SignatureImpl.java deleted file mode 100644 index 2958e00..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/SHA1withDSA_SignatureImpl.java +++ /dev/null @@ -1,423 +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 org.apache.harmony.security.provider.crypto; - -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.InvalidParameterException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import java.security.interfaces.DSAKey; -import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.DSAPublicKey; - -public class SHA1withDSA_SignatureImpl extends Signature { - - private MessageDigest msgDigest; - - private DSAKey dsaKey; - - /** - * The solo constructor. - */ - public SHA1withDSA_SignatureImpl() throws NoSuchAlgorithmException { - - super("SHA1withDSA"); - - msgDigest = MessageDigest.getInstance("SHA1"); - } - - /** - * Deprecated method. - * - * @return - * null - */ - protected Object engineGetParameter(String param) - throws InvalidParameterException { - if (param == null) { - throw new NullPointerException("param == null"); - } - return null; - } - - /** - * Initializes this signature object with PrivateKey object - * passed as argument to the method. - * - * @params - * privateKey DSAPrivateKey object - * @throws - * InvalidKeyException if privateKey is not DSAPrivateKey object - */ - protected void engineInitSign(PrivateKey privateKey) - throws InvalidKeyException { - - DSAParams params; - - // parameters and private key - BigInteger p, q, x; - - int n; - - if (privateKey == null || !(privateKey instanceof DSAPrivateKey)) { - throw new InvalidKeyException(); - } - - params = ((DSAPrivateKey) privateKey).getParams(); - p = params.getP(); - q = params.getQ(); - x = ((DSAPrivateKey) privateKey).getX(); - - // checks described in DSA standard - n = p.bitLength(); - if (p.compareTo(BigInteger.valueOf(1)) != 1 || n < 512 || n > 1024 || (n & 077) != 0) { - throw new InvalidKeyException("bad p"); - } - if (q.signum() != 1 && q.bitLength() != 160) { - throw new InvalidKeyException("bad q"); - } - if (x.signum() != 1 || x.compareTo(q) != -1) { - throw new InvalidKeyException("x <= 0 || x >= q"); - } - - dsaKey = (DSAKey) privateKey; - - msgDigest.reset(); - } - - /** - * Initializes this signature object with PublicKey object - * passed as argument to the method. - * - * @params - * publicKey DSAPublicKey object - * @throws - * InvalidKeyException if publicKey is not DSAPublicKey object - */ - protected void engineInitVerify(PublicKey publicKey) - throws InvalidKeyException { - - // parameters and public key - BigInteger p, q, y; - - int n1; - - if (publicKey == null || !(publicKey instanceof DSAPublicKey)) { - throw new InvalidKeyException("publicKey is not an instance of DSAPublicKey"); - } - - DSAParams params = ((DSAPublicKey) publicKey).getParams(); - p = params.getP(); - q = params.getQ(); - y = ((DSAPublicKey) publicKey).getY(); - - // checks described in DSA standard - n1 = p.bitLength(); - if (p.compareTo(BigInteger.valueOf(1)) != 1 || n1 < 512 || n1 > 1024 || (n1 & 077) != 0) { - throw new InvalidKeyException("bad p"); - } - if (q.signum() != 1 || q.bitLength() != 160) { - throw new InvalidKeyException("bad q"); - } - if (y.signum() != 1) { - throw new InvalidKeyException("y <= 0"); - } - - dsaKey = (DSAKey) publicKey; - - msgDigest.reset(); - } - - /* - * Deprecated method. - * - * @throws - * InvalidParameterException - */ - protected void engineSetParameter(String param, Object value) throws InvalidParameterException { - if (param == null) { - throw new NullPointerException("param == null"); - } - throw new InvalidParameterException("invalid parameter for this engine"); - } - - /** - * Returns signature bytes as byte array containing - * ASN1 representation for two BigInteger objects - * which is SEQUENCE of two INTEGERS. - * Length of sequence varies from less than 46 to 48. - * - * Resets object to the state it was in - * when previous call to either "initSign" method was called. - * - * @return - * byte array containing signature in ASN1 representation - * @throws - * SignatureException if object's state is not SIGN or - * signature algorithm cannot process data - */ - - protected byte[] engineSign() throws SignatureException { - - // names of below BigIntegers are the same as they are defined in DSA standard - BigInteger r = null; - BigInteger s = null; - BigInteger k = null; - - // parameters and private key - BigInteger p, q, g, x; - - // BigInteger for message digest - BigInteger digestBI; - - // various byte array being used in computing signature - byte[] randomBytes; - byte[] rBytes; - byte[] sBytes; - byte[] signature; - - int n, n1, n2; - - DSAParams params; - - if (appRandom == null) { - appRandom = new SecureRandom(); - } - - params = dsaKey.getParams(); - p = params.getP(); - q = params.getQ(); - g = params.getG(); - x = ((DSAPrivateKey) dsaKey).getX(); - - // forming signature according algorithm described in chapter 5 of DSA standard - - digestBI = new BigInteger(1, msgDigest.digest()); - - randomBytes = new byte[20]; - - for (;;) { - - appRandom.nextBytes(randomBytes); - - k = new BigInteger(1, randomBytes); - if (k.compareTo(q) != -1) { - continue; - } - r = g.modPow(k, p).mod(q); - if (r.signum() == 0) { - continue; - } - - s = k.modInverse(q).multiply(digestBI.add(x.multiply(r)).mod(q)) - .mod(q); - - if (s.signum() != 0) { - break; - } - } - - // forming signature's ASN1 representation which is SEQUENCE of two INTEGERs - // - rBytes = r.toByteArray(); - n1 = rBytes.length; - if ((rBytes[0] & 0x80) != 0) { - n1++; - } - sBytes = s.toByteArray(); - n2 = sBytes.length; - if ((sBytes[0] & 0x80) != 0) { - n2++; - } - - signature = new byte[6 + n1 + n2]; // 48 is max. possible length of signature - signature[0] = (byte) 0x30; // ASN1 SEQUENCE tag - signature[1] = (byte) (4 + n1 + n2); // total length of two INTEGERs - signature[2] = (byte) 0x02; // ASN1 INTEGER tag - signature[3] = (byte) n1; // length of r - signature[4 + n1] = (byte) 0x02; // ASN1 INTEGER tag - signature[5 + n1] = (byte) n2; // length of s - - if (n1 == rBytes.length) { - n = 4; - } else { - n = 5; - } - System.arraycopy(rBytes, 0, signature, n, rBytes.length); - - if (n2 == sBytes.length) { - n = 6 + n1; - } else { - n = 7 + n1; - } - System.arraycopy(sBytes, 0, signature, n, sBytes.length); - - return signature; - } - - /** - * Updates data to sign or to verify. - * - * @params - * b byte to update - * @throws - * SignatureException if object was not initialized for signing or verifying - */ - protected void engineUpdate(byte b) throws SignatureException { - - msgDigest.update(b); - } - - /** - * Updates data to sign or to verify. - * - * @params - * b byte array containing bytes to update - * @params - * off offset in byte array to start from - * @params - * len number of bytes to use for updating - * @throws - * SignatureException if object was not initialized for signing or verifying - */ - protected void engineUpdate(byte[] b, int off, int len) - throws SignatureException { - - msgDigest.update(b, off, len); - } - - private boolean checkSignature(byte[] sigBytes, int offset, int length) - throws SignatureException { - - // names of below BigIntegers are the same as they are defined in DSA standard - BigInteger r, s, w; - BigInteger u1, u2, v; - - // parameters and public key - BigInteger p, q, g, y; - - DSAParams params; - - int n1, n2; - - byte[] bytes; - byte[] digest; - - // checking up on signature's ASN1 - try { - byte dummy; - n1 = sigBytes[offset + 3]; - n2 = sigBytes[offset + n1 + 5]; - - if (sigBytes[offset + 0] != 0x30 || sigBytes[offset + 2] != 2 - || sigBytes[offset + n1 + 4] != 2 - || sigBytes[offset + 1] != (n1 + n2 + 4) || n1 > 21 - || n2 > 21 - || (length != 0 && (sigBytes[offset + 1] + 2) > length)) { - throw new SignatureException("signature bytes have invalid encoding"); - } - - dummy = sigBytes[5 + n1 + n2]; // to check length of sigBytes - } catch (ArrayIndexOutOfBoundsException e) { - throw new SignatureException("bad argument: byte[] is too small"); - } - - digest = msgDigest.digest(); - - bytes = new byte[n1]; - System.arraycopy(sigBytes, offset + 4, bytes, 0, n1); - r = new BigInteger(bytes); - - bytes = new byte[n2]; - System.arraycopy(sigBytes, offset + 6 + n1, bytes, 0, n2); - s = new BigInteger(bytes); - - params = dsaKey.getParams(); - p = params.getP(); - q = params.getQ(); - g = params.getG(); - y = ((DSAPublicKey) dsaKey).getY(); - - // forming signature according algorithm described in chapter 6 of DSA standard - - if (r.signum() != 1 || r.compareTo(q) != -1 || s.signum() != 1 - || s.compareTo(q) != -1) { - return false; - } - - w = s.modInverse(q); - - u1 = (new BigInteger(1, digest)).multiply(w).mod(q); - u2 = r.multiply(w).mod(q); - - v = g.modPow(u1, p).multiply(y.modPow(u2, p)).mod(p).mod(q); - - if (v.compareTo(r) != 0) { - return false; - } - return true; - } - - /** - * Verifies the signature bytes. - * - * @params - * sigBytes byte array with signature bytes to verify. - * @return - * true if signature bytes were verified, false otherwise - * @throws - * SignatureException if object's state is not VERIFY or - * signature format is not ASN1 representation or - * signature algorithm cannot process data - */ - protected boolean engineVerify(byte[] sigBytes) throws SignatureException { - if (sigBytes == null) { - throw new NullPointerException("sigBytes == null"); - } - - return checkSignature(sigBytes, 0, 0); - } - - /** - * Verifies the signature bytes. - * - * @params - * sigBytes byte array with signature bytes to verify. - * @params - * offset index in sigBytes to start from - * @params - * length number of bytes allotted for signature - * @return - * true if signature bytes were verified, false otherwise - * @throws - * SignatureException if object's state is not VERIFY or - * signature format is not ASN1 representation or - * signature algorithm cannot process data - */ - protected boolean engineVerify(byte[] sigBytes, int offset, int length) - throws SignatureException { - return checkSignature(sigBytes, offset, length); - } -} diff --git a/luni/src/main/java/org/apache/harmony/security/provider/crypto/ThreeIntegerSequence.java b/luni/src/main/java/org/apache/harmony/security/provider/crypto/ThreeIntegerSequence.java deleted file mode 100644 index 4f4232a..0000000 --- a/luni/src/main/java/org/apache/harmony/security/provider/crypto/ThreeIntegerSequence.java +++ /dev/null @@ -1,73 +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 org.apache.harmony.security.provider.crypto; - -import org.apache.harmony.security.asn1.ASN1Integer; -import org.apache.harmony.security.asn1.ASN1Sequence; -import org.apache.harmony.security.asn1.ASN1Type; -import org.apache.harmony.security.asn1.BerInputStream; - - -/** - * The auxiliary class providing means to process ASN1Sequence of three Integers. - * Such sequences are parts of ASN1 encoded formats for DSA private and public keys. - */ -class ThreeIntegerSequence { - - byte[] p, q, g; - - private byte[] encoding; - - ThreeIntegerSequence(byte[] p, byte[] q, byte[] g) { - - this.p = p; - this.q = q; - this.g = g; - encoding = null; - } - - public byte[] getEncoded() { - if (encoding == null) { - encoding = ASN1.encode(this); - } - return encoding; - } - - public static final ASN1Sequence ASN1 = new ASN1Sequence(new ASN1Type[] { - ASN1Integer.getInstance(), ASN1Integer.getInstance(), - ASN1Integer.getInstance() }) { - - protected Object getDecodedObject(BerInputStream in) { - - Object[] values = (Object[]) in.content; - - return new ThreeIntegerSequence((byte[]) values[0], - (byte[]) values[1], (byte[]) values[2]); - } - - protected void getValues(Object object, Object[] values) { - - ThreeIntegerSequence mySeq = (ThreeIntegerSequence) object; - - values[0] = mySeq.p; - values[1] = mySeq.q; - values[2] = mySeq.g; - } - }; -} 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 135394d..e7f3596 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 @@ -21,6 +21,7 @@ */ package org.apache.harmony.security.utils; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; @@ -30,10 +31,12 @@ import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.Signature; import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import javax.security.auth.x500.X500Principal; @@ -42,7 +45,6 @@ import org.apache.harmony.security.asn1.BerInputStream; import org.apache.harmony.security.pkcs7.ContentInfo; import org.apache.harmony.security.pkcs7.SignedData; import org.apache.harmony.security.pkcs7.SignerInfo; -import org.apache.harmony.security.provider.cert.X509CertImpl; import org.apache.harmony.security.x501.AttributeTypeAndValue; public class JarUtils { @@ -53,27 +55,18 @@ public class JarUtils { new int[] {1, 2, 840, 113549, 1, 9, 4}; /** - * @see #verifySignature(InputStream, InputStream, boolean) - */ - public static Certificate[] verifySignature(InputStream signature, InputStream signatureBlock) - throws IOException, GeneralSecurityException { - return verifySignature(signature, signatureBlock, false); - } - - /** * This method handle all the work with PKCS7, ASN1 encoding, signature verifying, * and certification path building. * See also PKCS #7: Cryptographic Message Syntax Standard: * http://www.ietf.org/rfc/rfc2315.txt * @param signature - the input stream of signature file to be verified * @param signatureBlock - the input stream of corresponding signature block file - * @param chainCheck - whether to validate certificate chain signatures * @return array of certificates used to verify the signature file * @throws IOException - if some errors occurs during reading from the stream * @throws GeneralSecurityException - if signature verification process fails */ public static Certificate[] verifySignature(InputStream signature, InputStream - signatureBlock, boolean chainCheck) throws IOException, GeneralSecurityException { + signatureBlock) throws IOException, GeneralSecurityException { BerInputStream bis = new BerInputStream(signatureBlock); ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis); @@ -87,9 +80,13 @@ public class JarUtils { return null; } X509Certificate[] certs = new X509Certificate[encCerts.size()]; + CertificateFactory cf = CertificateFactory.getInstance("X.509"); int i = 0; for (org.apache.harmony.security.x509.Certificate encCert : encCerts) { - certs[i++] = new X509CertImpl(encCert); + final byte[] encoded = encCert.getEncoded(); + final InputStream is = new ByteArrayInputStream(encoded); + certs[i++] = new VerbatimX509Certificate((X509Certificate) cf.generateCertificate(is), + encoded); } List<SignerInfo> sigInfos = signedData.getSignerInfos(); @@ -131,17 +128,16 @@ public class JarUtils { String alg = null; Signature sig = null; - if (daOid != null && deaOid != null) { - alg = daOid + "with" + deaOid; + if (deaOid != null) { + alg = deaOid; try { sig = Signature.getInstance(alg); } catch (NoSuchAlgorithmException e) { } - // Try to convert to names instead of OID. - if (sig == null) { - final String deaName = sigInfo.getDigestEncryptionAlgorithmName(); - alg = daName + "with" + deaName; + final String deaName = sigInfo.getDigestEncryptionAlgorithmName(); + if (sig == null && deaName != null) { + alg = deaName; try { sig = Signature.getInstance(alg); } catch (NoSuchAlgorithmException e) { @@ -149,19 +145,17 @@ public class JarUtils { } } - /* - * TODO figure out the case in which we'd only use digestAlgorithm and - * add a test for it. - */ - if (sig == null && daOid != null) { - alg = daOid; + if (sig == null && daOid != null && deaOid != null) { + alg = daOid + "with" + deaOid; try { sig = Signature.getInstance(alg); } catch (NoSuchAlgorithmException e) { } - if (sig == null && daName != null) { - alg = daName; + // Try to convert to names instead of OID. + if (sig == null) { + final String deaName = sigInfo.getDigestEncryptionAlgorithmName(); + alg = daName + "with" + deaName; try { sig = Signature.getInstance(alg); } catch (NoSuchAlgorithmException e) { @@ -232,54 +226,63 @@ public class JarUtils { throw new SecurityException("Incorrect signature"); } - return createChain(certs[issuerSertIndex], certs, chainCheck); + return createChain(certs[issuerSertIndex], certs); } - private static X509Certificate[] createChain(X509Certificate signer, - X509Certificate[] candidates, boolean chainCheck) { - LinkedList chain = new LinkedList(); - chain.add(0, signer); + private static X509Certificate[] createChain(X509Certificate signer, + X509Certificate[] candidates) { + Principal issuer = signer.getIssuerDN(); // Signer is self-signed - if (signer.getSubjectDN().equals(signer.getIssuerDN())){ - return (X509Certificate[])chain.toArray(new X509Certificate[1]); + if (signer.getSubjectDN().equals(issuer)) { + return new X509Certificate[] { signer }; } - Principal issuer = signer.getIssuerDN(); + ArrayList<X509Certificate> chain = new ArrayList<X509Certificate>(candidates.length + 1); + chain.add(0, signer); + X509Certificate issuerCert; - X509Certificate subjectCert = signer; int count = 1; while (true) { - issuerCert = findCert(issuer, candidates, subjectCert, chainCheck); - if( issuerCert == null) { + issuerCert = findCert(issuer, candidates); + if (issuerCert == null) { break; } chain.add(issuerCert); count++; - if (issuerCert.getSubjectDN().equals(issuerCert.getIssuerDN())) { + issuer = issuerCert.getIssuerDN(); + if (issuerCert.getSubjectDN().equals(issuer)) { break; } - issuer = issuerCert.getIssuerDN(); - subjectCert = issuerCert; } - return (X509Certificate[])chain.toArray(new X509Certificate[count]); + return chain.toArray(new X509Certificate[count]); } - private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates, - X509Certificate subjectCert, boolean chainCheck) { + private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates) { for (int i = 0; i < candidates.length; i++) { if (issuer.equals(candidates[i].getSubjectDN())) { - if (chainCheck) { - try { - subjectCert.verify(candidates[i].getPublicKey()); - } catch (Exception e) { - continue; - } - } return candidates[i]; } } return null; } + /** + * For legacy reasons we need to return exactly the original encoded + * certificate bytes, instead of letting the underlying implementation have + * a shot at re-encoding the data. + */ + private static class VerbatimX509Certificate extends WrappedX509Certificate { + private byte[] encodedVerbatim; + + public VerbatimX509Certificate(X509Certificate wrapped, byte[] encodedVerbatim) { + super(wrapped); + this.encodedVerbatim = encodedVerbatim; + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return encodedVerbatim; + } + } } 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 new file mode 100644 index 0000000..2b09309 --- /dev/null +++ b/luni/src/main/java/org/apache/harmony/security/utils/WrappedX509Certificate.java @@ -0,0 +1,175 @@ +/* + * 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 org.apache.harmony.security.utils; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Set; + +public class WrappedX509Certificate extends X509Certificate { + private final X509Certificate wrapped; + + public WrappedX509Certificate(X509Certificate wrapped) { + this.wrapped = wrapped; + } + + @Override + public Set<String> getCriticalExtensionOIDs() { + return wrapped.getCriticalExtensionOIDs(); + } + + @Override + public byte[] getExtensionValue(String oid) { + return wrapped.getExtensionValue(oid); + } + + @Override + public Set<String> getNonCriticalExtensionOIDs() { + return wrapped.getNonCriticalExtensionOIDs(); + } + + @Override + public boolean hasUnsupportedCriticalExtension() { + return wrapped.hasUnsupportedCriticalExtension(); + } + + @Override + public void checkValidity() throws CertificateExpiredException, + CertificateNotYetValidException { + wrapped.checkValidity(); + } + + @Override + public void checkValidity(Date date) throws CertificateExpiredException, + CertificateNotYetValidException { + wrapped.checkValidity(date); + } + + @Override + public int getVersion() { + return wrapped.getVersion(); + } + + @Override + public BigInteger getSerialNumber() { + return wrapped.getSerialNumber(); + } + + @Override + public Principal getIssuerDN() { + return wrapped.getIssuerDN(); + } + + @Override + public Principal getSubjectDN() { + return wrapped.getSubjectDN(); + } + + @Override + public Date getNotBefore() { + return wrapped.getNotBefore(); + } + + @Override + public Date getNotAfter() { + return wrapped.getNotAfter(); + } + + @Override + public byte[] getTBSCertificate() throws CertificateEncodingException { + return wrapped.getTBSCertificate(); + } + + @Override + public byte[] getSignature() { + return wrapped.getSignature(); + } + + @Override + public String getSigAlgName() { + return wrapped.getSigAlgName(); + } + + @Override + public String getSigAlgOID() { + return wrapped.getSigAlgOID(); + } + + @Override + public byte[] getSigAlgParams() { + return wrapped.getSigAlgParams(); + } + + @Override + public boolean[] getIssuerUniqueID() { + return wrapped.getIssuerUniqueID(); + } + + @Override + public boolean[] getSubjectUniqueID() { + return wrapped.getSubjectUniqueID(); + } + + @Override + public boolean[] getKeyUsage() { + return wrapped.getKeyUsage(); + } + + @Override + public int getBasicConstraints() { + return wrapped.getBasicConstraints(); + } + + @Override + public byte[] getEncoded() throws CertificateEncodingException { + return wrapped.getEncoded(); + } + + @Override + public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, + InvalidKeyException, NoSuchProviderException, SignatureException { + wrapped.verify(key); + } + + @Override + public void verify(PublicKey key, String sigProvider) throws CertificateException, + NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, + SignatureException { + verify(key, sigProvider); + } + + @Override + public String toString() { + return wrapped.toString(); + } + + @Override + public PublicKey getPublicKey() { + return wrapped.getPublicKey(); + } +} diff --git a/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java b/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java index 3b5f622..171d9c2 100644 --- a/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java +++ b/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValue.java @@ -287,6 +287,8 @@ public final class AttributeTypeAndValue { } else { if (X500Principal.CANONICAL.equals(attrFormat)) { sb.append(value.makeCanonical()); + } else if (X500Principal.RFC2253.equals(attrFormat)) { + sb.append(value.getRFC2253String()); } else { sb.append(value.escapedString); } diff --git a/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java b/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java index 160c62d..bdc3c84 100644 --- a/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java +++ b/luni/src/main/java/org/apache/harmony/security/x501/AttributeTypeAndValueComparator.java @@ -30,27 +30,26 @@ import org.apache.harmony.security.utils.ObjectIdentifier; * AttributeTypeAndValue comparator * */ -public class AttributeTypeAndValueComparator implements Comparator, Serializable { +public class AttributeTypeAndValueComparator implements Comparator<AttributeTypeAndValue>, + Serializable { private static final long serialVersionUID = -1286471842007103132L; /** * compares two AttributeTypeAndValues * - * @param obj1 + * @param atav1 * first AttributeTypeAndValue - * @param obj2 + * @param atav2 * second AttributeTypeAndValue * @return -1 of first AttributeTypeAndValue "less" than second * AttributeTypeAndValue 1 otherwise, 0 if they are equal */ - public int compare(Object obj1, Object obj2) { - if (obj1 == obj2) { + public int compare(AttributeTypeAndValue atav1, AttributeTypeAndValue atav2) { + if (atav1 == atav2) { return 0; } - AttributeTypeAndValue atav1 = (AttributeTypeAndValue) obj1; - AttributeTypeAndValue atav2 = (AttributeTypeAndValue) obj2; String kw1 = atav1.getType().getName(); String kw2 = atav2.getType().getName(); if (kw1 != null && kw2 == null) { diff --git a/luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java b/luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java index 63be3f1..b3eb200 100644 --- a/luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java +++ b/luni/src/main/java/org/apache/harmony/security/x501/AttributeValue.java @@ -37,8 +37,12 @@ public final class AttributeValue { public boolean wasEncoded; + private boolean hasConsecutiveSpaces; + public final String escapedString; + private String rfc2253String; + private String hexString; private final int tag; @@ -197,8 +201,9 @@ public final class AttributeValue { * Escapes: * 1) chars ",", "+", """, "\", "<", ">", ";" (RFC 2253) * 2) chars "#", "=" (required by RFC 1779) - * 3) a space char at the beginning or end - * 4) according to the requirement to be RFC 1779 compatible: + * 3) leading or trailing spaces + * 4) consecutive spaces (RFC 1779) + * 5) according to the requirement to be RFC 1779 compatible: * '#' char is escaped in any position */ private String makeEscaped(String name) { @@ -208,14 +213,35 @@ public final class AttributeValue { } StringBuilder buf = new StringBuilder(length * 2); + // Keeps track of whether we are escaping spaces. + boolean escapeSpaces = false; + for (int index = 0; index < length; index++) { char ch = name.charAt(index); switch (ch) { case ' ': - if (index == 0 || index == (length - 1)) { - // escape first or last space + /* + * We should escape spaces in the following cases: + * 1) at the beginning + * 2) at the end + * 3) consecutive spaces + * Since multiple spaces at the beginning or end will be covered by + * 3, we don't need a special case to check for that. Note that RFC 2253 + * doesn't escape consecutive spaces, so they are removed in + * getRFC2253String instead of making two different strings here. + */ + if (index < (length - 1)) { + boolean nextIsSpace = name.charAt(index + 1) == ' '; + escapeSpaces = escapeSpaces || nextIsSpace || index == 0; + hasConsecutiveSpaces |= nextIsSpace; + } else { + escapeSpaces = true; + } + + if (escapeSpaces) { buf.append('\\'); } + buf.append(' '); break; @@ -241,6 +267,10 @@ public final class AttributeValue { buf.append(ch); break; } + + if (escapeSpaces && ch != ' ') { + escapeSpaces = false; + } } return buf.toString(); @@ -295,4 +325,50 @@ public final class AttributeValue { return buf.toString(); } + + /** + * Removes escape sequences used in RFC1779 escaping but not in RFC2253 and + * returns the RFC2253 string to the caller.. + */ + public String getRFC2253String() { + if (!hasConsecutiveSpaces) { + return escapedString; + } + + if (rfc2253String == null) { + // Scan backwards first since runs of spaces at the end are escaped. + int lastIndex = escapedString.length() - 2; + for (int i = lastIndex; i > 0; i -= 2) { + if (escapedString.charAt(i) == '\\' && escapedString.charAt(i + 1) == ' ') { + lastIndex = i - 1; + } + } + + boolean beginning = true; + StringBuilder sb = new StringBuilder(escapedString.length()); + for (int i = 0; i < escapedString.length(); i++) { + char ch = escapedString.charAt(i); + if (ch != '\\') { + sb.append(ch); + beginning = false; + } else { + char nextCh = escapedString.charAt(i + 1); + if (nextCh == ' ') { + if (beginning || i > lastIndex) { + sb.append(ch); + } + sb.append(nextCh); + } else { + sb.append(ch); + sb.append(nextCh); + beginning = false; + } + + i++; + } + } + rfc2253String = sb.toString(); + } + return rfc2253String; + } } diff --git a/luni/src/main/java/org/apache/harmony/security/x509/Extension.java b/luni/src/main/java/org/apache/harmony/security/x509/Extension.java index d9b02f9..d5d8015 100644 --- a/luni/src/main/java/org/apache/harmony/security/x509/Extension.java +++ b/luni/src/main/java/org/apache/harmony/security/x509/Extension.java @@ -23,6 +23,7 @@ package org.apache.harmony.security.x509; import java.io.IOException; +import java.io.OutputStream; import java.util.Arrays; import org.apache.harmony.security.asn1.ASN1Boolean; import org.apache.harmony.security.asn1.ASN1OctetString; @@ -49,7 +50,7 @@ import org.apache.harmony.security.utils.Array; * } * </pre> */ -public final class Extension { +public final class Extension implements java.security.cert.Extension { // critical constants public static final boolean CRITICAL = true; public static final boolean NON_CRITICAL = false; @@ -145,7 +146,8 @@ public final class Extension { /** * Returns the value of extnID field of the structure. */ - public String getExtnID() { + @Override + public String getId() { if (extnID_str == null) { extnID_str = ObjectIdentifier.toString(extnID); } @@ -155,14 +157,16 @@ public final class Extension { /** * Returns the value of critical field of the structure. */ - public boolean getCritical() { + @Override + public boolean isCritical() { return critical; } /** * Returns the value of extnValue field of the structure. */ - public byte[] getExtnValue() { + @Override + public byte[] getValue() { return extnValue; } @@ -187,6 +191,11 @@ public final class Extension { return encoding; } + @Override + public void encode(OutputStream out) throws IOException { + out.write(getEncoded()); + } + @Override public boolean equals(Object ext) { if (!(ext instanceof Extension)) { return false; @@ -287,7 +296,7 @@ public final class Extension { } public void dumpValue(StringBuilder sb, String prefix) { - sb.append("OID: ").append(getExtnID()).append(", Critical: ").append(critical).append('\n'); + sb.append("OID: ").append(getId()).append(", Critical: ").append(critical).append('\n'); if (!valueDecoded) { try { decodeExtensionValue(); diff --git a/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java b/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java index 92ff3a9..7a10ebc 100644 --- a/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java +++ b/luni/src/main/java/org/apache/harmony/security/x509/Extensions.java @@ -136,8 +136,8 @@ public final class Extensions { Set<String> localNoncritical = new HashSet<String>(size); Boolean localHasUnsupported = Boolean.FALSE; for (Extension extension : extensions) { - String oid = extension.getExtnID(); - if (extension.getCritical()) { + String oid = extension.getId(); + if (extension.isCritical()) { if (!SUPPORTED_CRITICAL.contains(oid)) { localHasUnsupported = Boolean.TRUE; } @@ -162,7 +162,7 @@ public final class Extensions { if (localOidMap == null) { localOidMap = new HashMap<String, Extension>(); for (Extension extension : extensions) { - localOidMap.put(extension.getExtnID(), extension); + localOidMap.put(extension.getId(), extension); } this.oidMap = localOidMap; } @@ -311,7 +311,7 @@ public final class Extensions { } Collection<List<?>> collection = ((GeneralNames) GeneralNames.ASN1.decode(extension - .getExtnValue())).getPairsList(); + .getValue())).getPairsList(); /* * If the extension had any invalid entries, we may have an empty diff --git a/luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java b/luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java index b7c1847..533c79c 100644 --- a/luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java +++ b/luni/src/main/java/org/apache/harmony/security/x509/InvalidityDate.java @@ -44,6 +44,13 @@ public final class InvalidityDate extends ExtensionValue { } /** + * Constructs the object from a date instance. + */ + public InvalidityDate(Date date) { + this.date = (Date) date.clone(); + } + + /** * Returns the invalidity date. */ public Date getDate() { diff --git a/luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java b/luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java index 183ecde..2c9d6aa 100644 --- a/luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java +++ b/luni/src/main/java/org/apache/harmony/security/x509/ReasonCode.java @@ -18,6 +18,7 @@ package org.apache.harmony.security.x509; import java.io.IOException; +import java.security.cert.CRLReason; import org.apache.harmony.security.asn1.ASN1Enumerated; import org.apache.harmony.security.asn1.ASN1Type; @@ -71,6 +72,14 @@ public final class ReasonCode extends ExtensionValue { return encoding; } + public CRLReason getReason() { + CRLReason[] values = CRLReason.values(); + if (code < 0 || code > values.length) { + return null; + } + return values[code]; + } + @Override public void dumpValue(StringBuilder sb, String prefix) { sb.append(prefix).append("Reason Code: [ "); switch (code) { diff --git a/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java b/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java index db6f4ef..fa6308e 100644 --- a/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java +++ b/luni/src/main/java/org/apache/harmony/xml/ExpatParser.java @@ -48,7 +48,7 @@ class ExpatParser { private boolean inStartElement = false; private int attributeCount = -1; - private int attributePointer = 0; + private long attributePointer = 0; private final Locator locator = new ExpatLocator(); @@ -129,7 +129,7 @@ class ExpatParser { * @param attributeCount number of attributes */ /*package*/ void startElement(String uri, String localName, String qName, - int attributePointer, int attributeCount) throws SAXException { + long attributePointer, int attributeCount) throws SAXException { ContentHandler contentHandler = xmlReader.contentHandler; if (contentHandler == null) { return; @@ -772,7 +772,7 @@ class ExpatParser { @Override void startElement(String uri, String localName, String qName, - int attributePointer, int attributeCount) throws SAXException { + long attributePointer, int attributeCount) throws SAXException { /* * Skip topmost element generated by our workaround in * {@link #handleExternalEntity}. |