# HG changeset patch # User Jaroslav Tulach # Date 1380807790 -7200 # Node ID f194f314cac02db4cc139ca14140bf7489061eb0 # Parent 8cb6ebbd48234f00ec96075949a8f8c3da88adae# Parent 588d5bf7a5601660c2124a269afe88758fbcb784 Merging JDK classes need to run javac into default branch - don't compile yet diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/BufferedInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/BufferedInputStream.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,478 @@ +/* + * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +/** + * A BufferedInputStream adds + * functionality to another input stream-namely, + * the ability to buffer the input and to + * support the mark and reset + * methods. When the BufferedInputStream + * is created, an internal buffer array is + * created. As bytes from the stream are read + * or skipped, the internal buffer is refilled + * as necessary from the contained input stream, + * many bytes at a time. The mark + * operation remembers a point in the input + * stream and the reset operation + * causes all the bytes read since the most + * recent mark operation to be + * reread before new bytes are taken from + * the contained input stream. + * + * @author Arthur van Hoff + * @since JDK1.0 + */ +public +class BufferedInputStream extends FilterInputStream { + + private static int defaultBufferSize = 8192; + + /** + * The internal buffer array where the data is stored. When necessary, + * it may be replaced by another array of + * a different size. + */ + protected volatile byte buf[]; + + /** + * Atomic updater to provide compareAndSet for buf. This is + * necessary because closes can be asynchronous. We use nullness + * of buf[] as primary indicator that this stream is closed. (The + * "in" field is also nulled out on close.) + */ + private static final + AtomicReferenceFieldUpdater bufUpdater = + AtomicReferenceFieldUpdater.newUpdater + (BufferedInputStream.class, byte[].class, "buf"); + + /** + * The index one greater than the index of the last valid byte in + * the buffer. + * This value is always + * in the range 0 through buf.length; + * elements buf[0] through buf[count-1] + * contain buffered input data obtained + * from the underlying input stream. + */ + protected int count; + + /** + * The current position in the buffer. This is the index of the next + * character to be read from the buf array. + *

+ * This value is always in the range 0 + * through count. If it is less + * than count, then buf[pos] + * is the next byte to be supplied as input; + * if it is equal to count, then + * the next read or skip + * operation will require more bytes to be + * read from the contained input stream. + * + * @see java.io.BufferedInputStream#buf + */ + protected int pos; + + /** + * The value of the pos field at the time the last + * mark method was called. + *

+ * This value is always + * in the range -1 through pos. + * If there is no marked position in the input + * stream, this field is -1. If + * there is a marked position in the input + * stream, then buf[markpos] + * is the first byte to be supplied as input + * after a reset operation. If + * markpos is not -1, + * then all bytes from positions buf[markpos] + * through buf[pos-1] must remain + * in the buffer array (though they may be + * moved to another place in the buffer array, + * with suitable adjustments to the values + * of count, pos, + * and markpos); they may not + * be discarded unless and until the difference + * between pos and markpos + * exceeds marklimit. + * + * @see java.io.BufferedInputStream#mark(int) + * @see java.io.BufferedInputStream#pos + */ + protected int markpos = -1; + + /** + * The maximum read ahead allowed after a call to the + * mark method before subsequent calls to the + * reset method fail. + * Whenever the difference between pos + * and markpos exceeds marklimit, + * then the mark may be dropped by setting + * markpos to -1. + * + * @see java.io.BufferedInputStream#mark(int) + * @see java.io.BufferedInputStream#reset() + */ + protected int marklimit; + + /** + * Check to make sure that underlying input stream has not been + * nulled out due to close; if not return it; + */ + private InputStream getInIfOpen() throws IOException { + InputStream input = in; + if (input == null) + throw new IOException("Stream closed"); + return input; + } + + /** + * Check to make sure that buffer has not been nulled out due to + * close; if not return it; + */ + private byte[] getBufIfOpen() throws IOException { + byte[] buffer = buf; + if (buffer == null) + throw new IOException("Stream closed"); + return buffer; + } + + /** + * Creates a BufferedInputStream + * and saves its argument, the input stream + * in, for later use. An internal + * buffer array is created and stored in buf. + * + * @param in the underlying input stream. + */ + public BufferedInputStream(InputStream in) { + this(in, defaultBufferSize); + } + + /** + * Creates a BufferedInputStream + * with the specified buffer size, + * and saves its argument, the input stream + * in, for later use. An internal + * buffer array of length size + * is created and stored in buf. + * + * @param in the underlying input stream. + * @param size the buffer size. + * @exception IllegalArgumentException if size <= 0. + */ + public BufferedInputStream(InputStream in, int size) { + super(in); + if (size <= 0) { + throw new IllegalArgumentException("Buffer size <= 0"); + } + buf = new byte[size]; + } + + /** + * Fills the buffer with more data, taking into account + * shuffling and other tricks for dealing with marks. + * Assumes that it is being called by a synchronized method. + * This method also assumes that all data has already been read in, + * hence pos > count. + */ + private void fill() throws IOException { + byte[] buffer = getBufIfOpen(); + if (markpos < 0) + pos = 0; /* no mark: throw away the buffer */ + else if (pos >= buffer.length) /* no room left in buffer */ + if (markpos > 0) { /* can throw away early part of the buffer */ + int sz = pos - markpos; + System.arraycopy(buffer, markpos, buffer, 0, sz); + pos = sz; + markpos = 0; + } else if (buffer.length >= marklimit) { + markpos = -1; /* buffer got too big, invalidate mark */ + pos = 0; /* drop buffer contents */ + } else { /* grow buffer */ + int nsz = pos * 2; + if (nsz > marklimit) + nsz = marklimit; + byte nbuf[] = new byte[nsz]; + System.arraycopy(buffer, 0, nbuf, 0, pos); + if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { + // Can't replace buf if there was an async close. + // Note: This would need to be changed if fill() + // is ever made accessible to multiple threads. + // But for now, the only way CAS can fail is via close. + // assert buf == null; + throw new IOException("Stream closed"); + } + buffer = nbuf; + } + count = pos; + int n = getInIfOpen().read(buffer, pos, buffer.length - pos); + if (n > 0) + count = n + pos; + } + + /** + * See + * the general contract of the read + * method of InputStream. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if this input stream has been closed by + * invoking its {@link #close()} method, + * or an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public synchronized int read() throws IOException { + if (pos >= count) { + fill(); + if (pos >= count) + return -1; + } + return getBufIfOpen()[pos++] & 0xff; + } + + /** + * Read characters into a portion of an array, reading from the underlying + * stream at most once if necessary. + */ + private int read1(byte[] b, int off, int len) throws IOException { + int avail = count - pos; + if (avail <= 0) { + /* If the requested length is at least as large as the buffer, and + if there is no mark/reset activity, do not bother to copy the + bytes into the local buffer. In this way buffered streams will + cascade harmlessly. */ + if (len >= getBufIfOpen().length && markpos < 0) { + return getInIfOpen().read(b, off, len); + } + fill(); + avail = count - pos; + if (avail <= 0) return -1; + } + int cnt = (avail < len) ? avail : len; + System.arraycopy(getBufIfOpen(), pos, b, off, cnt); + pos += cnt; + return cnt; + } + + /** + * Reads bytes from this byte-input stream into the specified byte array, + * starting at the given offset. + * + *

This method implements the general contract of the corresponding + * {@link InputStream#read(byte[], int, int) read} method of + * the {@link InputStream} class. As an additional + * convenience, it attempts to read as many bytes as possible by repeatedly + * invoking the read method of the underlying stream. This + * iterated read continues until one of the following + * conditions becomes true:

If the first read on the underlying stream returns + * -1 to indicate end-of-file then this method returns + * -1. Otherwise this method returns the number of bytes + * actually read. + * + *

Subclasses of this class are encouraged, but not required, to + * attempt to read as many bytes as possible in the same fashion. + * + * @param b destination buffer. + * @param off offset at which to start storing bytes. + * @param len maximum number of bytes to read. + * @return the number of bytes read, or -1 if the end of + * the stream has been reached. + * @exception IOException if this input stream has been closed by + * invoking its {@link #close()} method, + * or an I/O error occurs. + */ + public synchronized int read(byte b[], int off, int len) + throws IOException + { + getBufIfOpen(); // Check for closed stream + if ((off | len | (off + len) | (b.length - (off + len))) < 0) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int n = 0; + for (;;) { + int nread = read1(b, off + n, len - n); + if (nread <= 0) + return (n == 0) ? nread : n; + n += nread; + if (n >= len) + return n; + // if not closed but no bytes available, return + InputStream input = in; + if (input != null && input.available() <= 0) + return n; + } + } + + /** + * See the general contract of the skip + * method of InputStream. + * + * @exception IOException if the stream does not support seek, + * or if this input stream has been closed by + * invoking its {@link #close()} method, or an + * I/O error occurs. + */ + public synchronized long skip(long n) throws IOException { + getBufIfOpen(); // Check for closed stream + if (n <= 0) { + return 0; + } + long avail = count - pos; + + if (avail <= 0) { + // If no mark position set then don't keep in buffer + if (markpos <0) + return getInIfOpen().skip(n); + + // Fill in buffer to save bytes for reset + fill(); + avail = count - pos; + if (avail <= 0) + return 0; + } + + long skipped = (avail < n) ? avail : n; + pos += skipped; + return skipped; + } + + /** + * Returns an estimate of the number of bytes that can be read (or + * skipped over) from this input stream without blocking by the next + * invocation of a method for this input stream. The next invocation might be + * the same thread or another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + *

+ * This method returns the sum of the number of bytes remaining to be read in + * the buffer (count - pos) and the result of calling the + * {@link java.io.FilterInputStream#in in}.available(). + * + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking. + * @exception IOException if this input stream has been closed by + * invoking its {@link #close()} method, + * or an I/O error occurs. + */ + public synchronized int available() throws IOException { + int n = count - pos; + int avail = getInIfOpen().available(); + return n > (Integer.MAX_VALUE - avail) + ? Integer.MAX_VALUE + : n + avail; + } + + /** + * See the general contract of the mark + * method of InputStream. + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + * @see java.io.BufferedInputStream#reset() + */ + public synchronized void mark(int readlimit) { + marklimit = readlimit; + markpos = pos; + } + + /** + * See the general contract of the reset + * method of InputStream. + *

+ * If markpos is -1 + * (no mark has been set or the mark has been + * invalidated), an IOException + * is thrown. Otherwise, pos is + * set equal to markpos. + * + * @exception IOException if this stream has not been marked or, + * if the mark has been invalidated, or the stream + * has been closed by invoking its {@link #close()} + * method, or an I/O error occurs. + * @see java.io.BufferedInputStream#mark(int) + */ + public synchronized void reset() throws IOException { + getBufIfOpen(); // Cause exception if closed + if (markpos < 0) + throw new IOException("Resetting to invalid mark"); + pos = markpos; + } + + /** + * Tests if this input stream supports the mark + * and reset methods. The markSupported + * method of BufferedInputStream returns + * true. + * + * @return a boolean indicating if this stream type supports + * the mark and reset methods. + * @see java.io.InputStream#mark(int) + * @see java.io.InputStream#reset() + */ + public boolean markSupported() { + return true; + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * Once the stream has been closed, further read(), available(), reset(), + * or skip() invocations will throw an IOException. + * Closing a previously closed stream has no effect. + * + * @exception IOException if an I/O error occurs. + */ + public void close() throws IOException { + byte[] buffer; + while ( (buffer = buf) != null) { + if (bufUpdater.compareAndSet(this, buffer, null)) { + InputStream input = in; + in = null; + if (input != null) + input.close(); + return; + } + // Else retry in case a new buf was CASed in fill() + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/FileDescriptor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileDescriptor.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,176 @@ +/* + * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +package java.io; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Instances of the file descriptor class serve as an opaque handle + * to the underlying machine-specific structure representing an open + * file, an open socket, or another source or sink of bytes. The + * main practical use for a file descriptor is to create a + * FileInputStream or FileOutputStream to + * contain it. + *

+ * Applications should not create their own file descriptors. + * + * @author Pavani Diwanji + * @see java.io.FileInputStream + * @see java.io.FileOutputStream + * @since JDK1.0 + */ +public final class FileDescriptor { + + private int fd; + + /** + * A counter for tracking the FIS/FOS/RAF instances that + * use this FileDescriptor. The FIS/FOS.finalize() will not release + * the FileDescriptor if it is still under user by a stream. + */ + private AtomicInteger useCount; + + /** + * Constructs an (invalid) FileDescriptor + * object. + */ + public /**/ FileDescriptor() { + fd = -1; + useCount = new AtomicInteger(); + } + + private /* */ FileDescriptor(int fd) { + this.fd = fd; + useCount = new AtomicInteger(); + } + + /** + * A handle to the standard input stream. Usually, this file + * descriptor is not used directly, but rather via the input stream + * known as System.in. + * + * @see java.lang.System#in + */ + public static final FileDescriptor in = new FileDescriptor(0); + + /** + * A handle to the standard output stream. Usually, this file + * descriptor is not used directly, but rather via the output stream + * known as System.out. + * @see java.lang.System#out + */ + public static final FileDescriptor out = new FileDescriptor(1); + + /** + * A handle to the standard error stream. Usually, this file + * descriptor is not used directly, but rather via the output stream + * known as System.err. + * + * @see java.lang.System#err + */ + public static final FileDescriptor err = new FileDescriptor(2); + + /** + * Tests if this file descriptor object is valid. + * + * @return true if the file descriptor object represents a + * valid, open file, socket, or other active I/O connection; + * false otherwise. + */ + public boolean valid() { + return fd != -1; + } + + /** + * Force all system buffers to synchronize with the underlying + * device. This method returns after all modified data and + * attributes of this FileDescriptor have been written to the + * relevant device(s). In particular, if this FileDescriptor + * refers to a physical storage medium, such as a file in a file + * system, sync will not return until all in-memory modified copies + * of buffers associated with this FileDescriptor have been + * written to the physical medium. + * + * sync is meant to be used by code that requires physical + * storage (such as a file) to be in a known state For + * example, a class that provided a simple transaction facility + * might use sync to ensure that all changes to a file caused + * by a given transaction were recorded on a storage medium. + * + * sync only affects buffers downstream of this FileDescriptor. If + * any in-memory buffering is being done by the application (for + * example, by a BufferedOutputStream object), those buffers must + * be flushed into the FileDescriptor (for example, by invoking + * OutputStream.flush) before that data will be affected by sync. + * + * @exception SyncFailedException + * Thrown when the buffers cannot be flushed, + * or because the system cannot guarantee that all the + * buffers have been synchronized with physical media. + * @since JDK1.1 + */ + public native void sync() throws SyncFailedException; + + /* This routine initializes JNI field offsets for the class */ + private static native void initIDs(); + + static { + initIDs(); + } + + // Set up JavaIOFileDescriptorAccess in SharedSecrets + static { + sun.misc.SharedSecrets.setJavaIOFileDescriptorAccess( + new sun.misc.JavaIOFileDescriptorAccess() { + public void set(FileDescriptor obj, int fd) { + obj.fd = fd; + } + + public int get(FileDescriptor obj) { + return obj.fd; + } + + public void setHandle(FileDescriptor obj, long handle) { + throw new UnsupportedOperationException(); + } + + public long getHandle(FileDescriptor obj) { + throw new UnsupportedOperationException(); + } + } + ); + } + + // package private methods used by FIS, FOS and RAF + + int incrementAndGetUseCount() { + return useCount.incrementAndGet(); + } + + int decrementAndGetUseCount() { + return useCount.decrementAndGet(); + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/FileInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileInputStream.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,408 @@ +/* + * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import java.nio.channels.FileChannel; +import sun.nio.ch.FileChannelImpl; + + +/** + * A FileInputStream obtains input bytes + * from a file in a file system. What files + * are available depends on the host environment. + * + *

FileInputStream is meant for reading streams of raw bytes + * such as image data. For reading streams of characters, consider using + * FileReader. + * + * @author Arthur van Hoff + * @see java.io.File + * @see java.io.FileDescriptor + * @see java.io.FileOutputStream + * @see java.nio.file.Files#newInputStream + * @since JDK1.0 + */ +public +class FileInputStream extends InputStream +{ + /* File Descriptor - handle to the open file */ + private final FileDescriptor fd; + + private FileChannel channel = null; + + private final Object closeLock = new Object(); + private volatile boolean closed = false; + + private static final ThreadLocal runningFinalize = + new ThreadLocal<>(); + + private static boolean isRunningFinalize() { + Boolean val; + if ((val = runningFinalize.get()) != null) + return val.booleanValue(); + return false; + } + + /** + * Creates a FileInputStream by + * opening a connection to an actual file, + * the file named by the path name name + * in the file system. A new FileDescriptor + * object is created to represent this file + * connection. + *

+ * First, if there is a security + * manager, its checkRead method + * is called with the name argument + * as its argument. + *

+ * If the named file does not exist, is a directory rather than a regular + * file, or for some other reason cannot be opened for reading then a + * FileNotFoundException is thrown. + * + * @param name the system-dependent file name. + * @exception FileNotFoundException if the file does not exist, + * is a directory rather than a regular file, + * or for some other reason cannot be opened for + * reading. + * @exception SecurityException if a security manager exists and its + * checkRead method denies read access + * to the file. + * @see java.lang.SecurityManager#checkRead(java.lang.String) + */ + public FileInputStream(String name) throws FileNotFoundException { + this(name != null ? new File(name) : null); + } + + /** + * Creates a FileInputStream by + * opening a connection to an actual file, + * the file named by the File + * object file in the file system. + * A new FileDescriptor object + * is created to represent this file connection. + *

+ * First, if there is a security manager, + * its checkRead method is called + * with the path represented by the file + * argument as its argument. + *

+ * If the named file does not exist, is a directory rather than a regular + * file, or for some other reason cannot be opened for reading then a + * FileNotFoundException is thrown. + * + * @param file the file to be opened for reading. + * @exception FileNotFoundException if the file does not exist, + * is a directory rather than a regular file, + * or for some other reason cannot be opened for + * reading. + * @exception SecurityException if a security manager exists and its + * checkRead method denies read access to the file. + * @see java.io.File#getPath() + * @see java.lang.SecurityManager#checkRead(java.lang.String) + */ + public FileInputStream(File file) throws FileNotFoundException { + String name = (file != null ? file.getPath() : null); + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkRead(name); + } + if (name == null) { + throw new NullPointerException(); + } + fd = new FileDescriptor(); + fd.incrementAndGetUseCount(); + open(name); + } + + /** + * Creates a FileInputStream by using the file descriptor + * fdObj, which represents an existing connection to an + * actual file in the file system. + *

+ * If there is a security manager, its checkRead method is + * called with the file descriptor fdObj as its argument to + * see if it's ok to read the file descriptor. If read access is denied + * to the file descriptor a SecurityException is thrown. + *

+ * If fdObj is null then a NullPointerException + * is thrown. + *

+ * This constructor does not throw an exception if fdObj + * is {@link java.io.FileDescriptor#valid() invalid}. + * However, if the methods are invoked on the resulting stream to attempt + * I/O on the stream, an IOException is thrown. + * + * @param fdObj the file descriptor to be opened for reading. + * @throws SecurityException if a security manager exists and its + * checkRead method denies read access to the + * file descriptor. + * @see SecurityManager#checkRead(java.io.FileDescriptor) + */ + public FileInputStream(FileDescriptor fdObj) { + SecurityManager security = System.getSecurityManager(); + if (fdObj == null) { + throw new NullPointerException(); + } + if (security != null) { + security.checkRead(fdObj); + } + fd = fdObj; + + /* + * FileDescriptor is being shared by streams. + * Ensure that it's GC'ed only when all the streams/channels are done + * using it. + */ + fd.incrementAndGetUseCount(); + } + + /** + * Opens the specified file for reading. + * @param name the name of the file + */ + private native void open(String name) throws FileNotFoundException; + + /** + * Reads a byte of data from this input stream. This method blocks + * if no input is yet available. + * + * @return the next byte of data, or -1 if the end of the + * file is reached. + * @exception IOException if an I/O error occurs. + */ + public native int read() throws IOException; + + /** + * Reads a subarray as a sequence of bytes. + * @param b the data to be written + * @param off the start offset in the data + * @param len the number of bytes that are written + * @exception IOException If an I/O error has occurred. + */ + private native int readBytes(byte b[], int off, int len) throws IOException; + + /** + * Reads up to b.length bytes of data from this input + * stream into an array of bytes. This method blocks until some input + * is available. + * + * @param b the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the file has been reached. + * @exception IOException if an I/O error occurs. + */ + public int read(byte b[]) throws IOException { + return readBytes(b, 0, b.length); + } + + /** + * Reads up to len bytes of data from this input stream + * into an array of bytes. If len is not zero, the method + * blocks until some input is available; otherwise, no + * bytes are read and 0 is returned. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array b + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the file has been reached. + * @exception NullPointerException If b is null. + * @exception IndexOutOfBoundsException If off is negative, + * len is negative, or len is greater than + * b.length - off + * @exception IOException if an I/O error occurs. + */ + public int read(byte b[], int off, int len) throws IOException { + return readBytes(b, off, len); + } + + /** + * Skips over and discards n bytes of data from the + * input stream. + * + *

The skip method may, for a variety of + * reasons, end up skipping over some smaller number of bytes, + * possibly 0. If n is negative, an + * IOException is thrown, even though the skip + * method of the {@link InputStream} superclass does nothing in this case. + * The actual number of bytes skipped is returned. + * + *

This method may skip more bytes than are remaining in the backing + * file. This produces no exception and the number of bytes skipped + * may include some number of bytes that were beyond the EOF of the + * backing file. Attempting to read from the stream after skipping past + * the end will result in -1 indicating the end of the file. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @exception IOException if n is negative, if the stream does not + * support seek, or if an I/O error occurs. + */ + public native long skip(long n) throws IOException; + + /** + * Returns an estimate of the number of remaining bytes that can be read (or + * skipped over) from this input stream without blocking by the next + * invocation of a method for this input stream. The next invocation might be + * the same thread or another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + * + *

In some cases, a non-blocking read (or skip) may appear to be + * blocked when it is merely slow, for example when reading large + * files over slow networks. + * + * @return an estimate of the number of remaining bytes that can be read + * (or skipped over) from this input stream without blocking. + * @exception IOException if this file input stream has been closed by calling + * {@code close} or an I/O error occurs. + */ + public native int available() throws IOException; + + /** + * Closes this file input stream and releases any system resources + * associated with the stream. + * + *

If this stream has an associated channel then the channel is closed + * as well. + * + * @exception IOException if an I/O error occurs. + * + * @revised 1.4 + * @spec JSR-51 + */ + public void close() throws IOException { + synchronized (closeLock) { + if (closed) { + return; + } + closed = true; + } + if (channel != null) { + /* + * Decrement the FD use count associated with the channel + * The use count is incremented whenever a new channel + * is obtained from this stream. + */ + fd.decrementAndGetUseCount(); + channel.close(); + } + + /* + * Decrement the FD use count associated with this stream + */ + int useCount = fd.decrementAndGetUseCount(); + + /* + * If FileDescriptor is still in use by another stream, the finalizer + * will not close it. + */ + if ((useCount <= 0) || !isRunningFinalize()) { + close0(); + } + } + + /** + * Returns the FileDescriptor + * object that represents the connection to + * the actual file in the file system being + * used by this FileInputStream. + * + * @return the file descriptor object associated with this stream. + * @exception IOException if an I/O error occurs. + * @see java.io.FileDescriptor + */ + public final FileDescriptor getFD() throws IOException { + if (fd != null) return fd; + throw new IOException(); + } + + /** + * Returns the unique {@link java.nio.channels.FileChannel FileChannel} + * object associated with this file input stream. + * + *

The initial {@link java.nio.channels.FileChannel#position() + * position} of the returned channel will be equal to the + * number of bytes read from the file so far. Reading bytes from this + * stream will increment the channel's position. Changing the channel's + * position, either explicitly or by reading, will change this stream's + * file position. + * + * @return the file channel associated with this file input stream + * + * @since 1.4 + * @spec JSR-51 + */ + public FileChannel getChannel() { + synchronized (this) { + if (channel == null) { + channel = FileChannelImpl.open(fd, true, false, this); + + /* + * Increment fd's use count. Invoking the channel's close() + * method will result in decrementing the use count set for + * the channel. + */ + fd.incrementAndGetUseCount(); + } + return channel; + } + } + + private static native void initIDs(); + + private native void close0() throws IOException; + + static { + initIDs(); + } + + /** + * Ensures that the close method of this file input stream is + * called when there are no more references to it. + * + * @exception IOException if an I/O error occurs. + * @see java.io.FileInputStream#close() + */ + protected void finalize() throws IOException { + if ((fd != null) && (fd != FileDescriptor.in)) { + + /* + * Finalizer should not release the FileDescriptor if another + * stream is still using it. If the user directly invokes + * close() then the FileDescriptor is also released. + */ + runningFinalize.set(Boolean.TRUE); + try { + close(); + } finally { + runningFinalize.set(Boolean.FALSE); + } + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/FileOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileOutputStream.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,451 @@ +/* + * Copyright (c) 1994, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import java.nio.channels.FileChannel; +import sun.nio.ch.FileChannelImpl; + + +/** + * A file output stream is an output stream for writing data to a + * File or to a FileDescriptor. Whether or not + * a file is available or may be created depends upon the underlying + * platform. Some platforms, in particular, allow a file to be opened + * for writing by only one FileOutputStream (or other + * file-writing object) at a time. In such situations the constructors in + * this class will fail if the file involved is already open. + * + *

FileOutputStream is meant for writing streams of raw bytes + * such as image data. For writing streams of characters, consider using + * FileWriter. + * + * @author Arthur van Hoff + * @see java.io.File + * @see java.io.FileDescriptor + * @see java.io.FileInputStream + * @see java.nio.file.Files#newOutputStream + * @since JDK1.0 + */ +public +class FileOutputStream extends OutputStream +{ + /** + * The system dependent file descriptor. + */ + private final FileDescriptor fd; + + /** + * True if the file is opened for append. + */ + private final boolean append; + + /** + * The associated channel, initalized lazily. + */ + private FileChannel channel; + + private final Object closeLock = new Object(); + private volatile boolean closed = false; + private static final ThreadLocal runningFinalize = + new ThreadLocal<>(); + + private static boolean isRunningFinalize() { + Boolean val; + if ((val = runningFinalize.get()) != null) + return val.booleanValue(); + return false; + } + + /** + * Creates a file output stream to write to the file with the + * specified name. A new FileDescriptor object is + * created to represent this file connection. + *

+ * First, if there is a security manager, its checkWrite + * method is called with name as its argument. + *

+ * If the file exists but is a directory rather than a regular file, does + * not exist but cannot be created, or cannot be opened for any other + * reason then a FileNotFoundException is thrown. + * + * @param name the system-dependent filename + * @exception FileNotFoundException if the file exists but is a directory + * rather than a regular file, does not exist but cannot + * be created, or cannot be opened for any other reason + * @exception SecurityException if a security manager exists and its + * checkWrite method denies write access + * to the file. + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + */ + public FileOutputStream(String name) throws FileNotFoundException { + this(name != null ? new File(name) : null, false); + } + + /** + * Creates a file output stream to write to the file with the specified + * name. If the second argument is true, then + * bytes will be written to the end of the file rather than the beginning. + * A new FileDescriptor object is created to represent this + * file connection. + *

+ * First, if there is a security manager, its checkWrite + * method is called with name as its argument. + *

+ * If the file exists but is a directory rather than a regular file, does + * not exist but cannot be created, or cannot be opened for any other + * reason then a FileNotFoundException is thrown. + * + * @param name the system-dependent file name + * @param append if true, then bytes will be written + * to the end of the file rather than the beginning + * @exception FileNotFoundException if the file exists but is a directory + * rather than a regular file, does not exist but cannot + * be created, or cannot be opened for any other reason. + * @exception SecurityException if a security manager exists and its + * checkWrite method denies write access + * to the file. + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + * @since JDK1.1 + */ + public FileOutputStream(String name, boolean append) + throws FileNotFoundException + { + this(name != null ? new File(name) : null, append); + } + + /** + * Creates a file output stream to write to the file represented by + * the specified File object. A new + * FileDescriptor object is created to represent this + * file connection. + *

+ * First, if there is a security manager, its checkWrite + * method is called with the path represented by the file + * argument as its argument. + *

+ * If the file exists but is a directory rather than a regular file, does + * not exist but cannot be created, or cannot be opened for any other + * reason then a FileNotFoundException is thrown. + * + * @param file the file to be opened for writing. + * @exception FileNotFoundException if the file exists but is a directory + * rather than a regular file, does not exist but cannot + * be created, or cannot be opened for any other reason + * @exception SecurityException if a security manager exists and its + * checkWrite method denies write access + * to the file. + * @see java.io.File#getPath() + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + */ + public FileOutputStream(File file) throws FileNotFoundException { + this(file, false); + } + + /** + * Creates a file output stream to write to the file represented by + * the specified File object. If the second argument is + * true, then bytes will be written to the end of the file + * rather than the beginning. A new FileDescriptor object is + * created to represent this file connection. + *

+ * First, if there is a security manager, its checkWrite + * method is called with the path represented by the file + * argument as its argument. + *

+ * If the file exists but is a directory rather than a regular file, does + * not exist but cannot be created, or cannot be opened for any other + * reason then a FileNotFoundException is thrown. + * + * @param file the file to be opened for writing. + * @param append if true, then bytes will be written + * to the end of the file rather than the beginning + * @exception FileNotFoundException if the file exists but is a directory + * rather than a regular file, does not exist but cannot + * be created, or cannot be opened for any other reason + * @exception SecurityException if a security manager exists and its + * checkWrite method denies write access + * to the file. + * @see java.io.File#getPath() + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + * @since 1.4 + */ + public FileOutputStream(File file, boolean append) + throws FileNotFoundException + { + String name = (file != null ? file.getPath() : null); + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkWrite(name); + } + if (name == null) { + throw new NullPointerException(); + } + this.fd = new FileDescriptor(); + this.append = append; + + fd.incrementAndGetUseCount(); + open(name, append); + } + + /** + * Creates a file output stream to write to the specified file + * descriptor, which represents an existing connection to an actual + * file in the file system. + *

+ * First, if there is a security manager, its checkWrite + * method is called with the file descriptor fdObj + * argument as its argument. + *

+ * If fdObj is null then a NullPointerException + * is thrown. + *

+ * This constructor does not throw an exception if fdObj + * is {@link java.io.FileDescriptor#valid() invalid}. + * However, if the methods are invoked on the resulting stream to attempt + * I/O on the stream, an IOException is thrown. + * + * @param fdObj the file descriptor to be opened for writing + * @exception SecurityException if a security manager exists and its + * checkWrite method denies + * write access to the file descriptor + * @see java.lang.SecurityManager#checkWrite(java.io.FileDescriptor) + */ + public FileOutputStream(FileDescriptor fdObj) { + SecurityManager security = System.getSecurityManager(); + if (fdObj == null) { + throw new NullPointerException(); + } + if (security != null) { + security.checkWrite(fdObj); + } + this.fd = fdObj; + this.append = false; + + /* + * FileDescriptor is being shared by streams. + * Ensure that it's GC'ed only when all the streams/channels are done + * using it. + */ + fd.incrementAndGetUseCount(); + } + + /** + * Opens a file, with the specified name, for overwriting or appending. + * @param name name of file to be opened + * @param append whether the file is to be opened in append mode + */ + private native void open(String name, boolean append) + throws FileNotFoundException; + + /** + * Writes the specified byte to this file output stream. + * + * @param b the byte to be written. + * @param append {@code true} if the write operation first + * advances the position to the end of file + */ + private native void write(int b, boolean append) throws IOException; + + /** + * Writes the specified byte to this file output stream. Implements + * the write method of OutputStream. + * + * @param b the byte to be written. + * @exception IOException if an I/O error occurs. + */ + public void write(int b) throws IOException { + write(b, append); + } + + /** + * Writes a sub array as a sequence of bytes. + * @param b the data to be written + * @param off the start offset in the data + * @param len the number of bytes that are written + * @param append {@code true} to first advance the position to the + * end of file + * @exception IOException If an I/O error has occurred. + */ + private native void writeBytes(byte b[], int off, int len, boolean append) + throws IOException; + + /** + * Writes b.length bytes from the specified byte array + * to this file output stream. + * + * @param b the data. + * @exception IOException if an I/O error occurs. + */ + public void write(byte b[]) throws IOException { + writeBytes(b, 0, b.length, append); + } + + /** + * Writes len bytes from the specified byte array + * starting at offset off to this file output stream. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @exception IOException if an I/O error occurs. + */ + public void write(byte b[], int off, int len) throws IOException { + writeBytes(b, off, len, append); + } + + /** + * Closes this file output stream and releases any system resources + * associated with this stream. This file output stream may no longer + * be used for writing bytes. + * + *

If this stream has an associated channel then the channel is closed + * as well. + * + * @exception IOException if an I/O error occurs. + * + * @revised 1.4 + * @spec JSR-51 + */ + public void close() throws IOException { + synchronized (closeLock) { + if (closed) { + return; + } + closed = true; + } + + if (channel != null) { + /* + * Decrement FD use count associated with the channel + * The use count is incremented whenever a new channel + * is obtained from this stream. + */ + fd.decrementAndGetUseCount(); + channel.close(); + } + + /* + * Decrement FD use count associated with this stream + */ + int useCount = fd.decrementAndGetUseCount(); + + /* + * If FileDescriptor is still in use by another stream, the finalizer + * will not close it. + */ + if ((useCount <= 0) || !isRunningFinalize()) { + close0(); + } + } + + /** + * Returns the file descriptor associated with this stream. + * + * @return the FileDescriptor object that represents + * the connection to the file in the file system being used + * by this FileOutputStream object. + * + * @exception IOException if an I/O error occurs. + * @see java.io.FileDescriptor + */ + public final FileDescriptor getFD() throws IOException { + if (fd != null) return fd; + throw new IOException(); + } + + /** + * Returns the unique {@link java.nio.channels.FileChannel FileChannel} + * object associated with this file output stream.

+ * + *

The initial {@link java.nio.channels.FileChannel#position() + * position} of the returned channel will be equal to the + * number of bytes written to the file so far unless this stream is in + * append mode, in which case it will be equal to the size of the file. + * Writing bytes to this stream will increment the channel's position + * accordingly. Changing the channel's position, either explicitly or by + * writing, will change this stream's file position. + * + * @return the file channel associated with this file output stream + * + * @since 1.4 + * @spec JSR-51 + */ + public FileChannel getChannel() { + synchronized (this) { + if (channel == null) { + channel = FileChannelImpl.open(fd, false, true, append, this); + + /* + * Increment fd's use count. Invoking the channel's close() + * method will result in decrementing the use count set for + * the channel. + */ + fd.incrementAndGetUseCount(); + } + return channel; + } + } + + /** + * Cleans up the connection to the file, and ensures that the + * close method of this file output stream is + * called when there are no more references to this stream. + * + * @exception IOException if an I/O error occurs. + * @see java.io.FileInputStream#close() + */ + protected void finalize() throws IOException { + if (fd != null) { + if (fd == FileDescriptor.out || fd == FileDescriptor.err) { + flush(); + } else { + + /* + * Finalizer should not release the FileDescriptor if another + * stream is still using it. If the user directly invokes + * close() then the FileDescriptor is also released. + */ + runningFinalize.set(Boolean.TRUE); + try { + close(); + } finally { + runningFinalize.set(Boolean.FALSE); + } + } + } + } + + private native void close0() throws IOException; + + private static native void initIDs(); + + static { + initIDs(); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/FileReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileReader.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,85 @@ +/* + * Copyright (c) 1996, 2001, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * Convenience class for reading character files. The constructors of this + * class assume that the default character encoding and the default byte-buffer + * size are appropriate. To specify these values yourself, construct an + * InputStreamReader on a FileInputStream. + * + *

FileReader is meant for reading streams of characters. + * For reading streams of raw bytes, consider using a + * FileInputStream. + * + * @see InputStreamReader + * @see FileInputStream + * + * @author Mark Reinhold + * @since JDK1.1 + */ +public class FileReader extends InputStreamReader { + + /** + * Creates a new FileReader, given the name of the + * file to read from. + * + * @param fileName the name of the file to read from + * @exception FileNotFoundException if the named file does not exist, + * is a directory rather than a regular file, + * or for some other reason cannot be opened for + * reading. + */ + public FileReader(String fileName) throws FileNotFoundException { + super(new FileInputStream(fileName)); + } + + /** + * Creates a new FileReader, given the File + * to read from. + * + * @param file the File to read from + * @exception FileNotFoundException if the file does not exist, + * is a directory rather than a regular file, + * or for some other reason cannot be opened for + * reading. + */ + public FileReader(File file) throws FileNotFoundException { + super(new FileInputStream(file)); + } + + /** + * Creates a new FileReader, given the + * FileDescriptor to read from. + * + * @param fd the FileDescriptor to read from + */ + public FileReader(FileDescriptor fd) { + super(new FileInputStream(fd)); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/FileWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/FileWriter.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,119 @@ +/* + * Copyright (c) 1996, 2001, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * Convenience class for writing character files. The constructors of this + * class assume that the default character encoding and the default byte-buffer + * size are acceptable. To specify these values yourself, construct an + * OutputStreamWriter on a FileOutputStream. + * + *

Whether or not a file is available or may be created depends upon the + * underlying platform. Some platforms, in particular, allow a file to be + * opened for writing by only one FileWriter (or other file-writing + * object) at a time. In such situations the constructors in this class + * will fail if the file involved is already open. + * + *

FileWriter is meant for writing streams of characters. + * For writing streams of raw bytes, consider using a + * FileOutputStream. + * + * @see OutputStreamWriter + * @see FileOutputStream + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class FileWriter extends OutputStreamWriter { + + /** + * Constructs a FileWriter object given a file name. + * + * @param fileName String The system-dependent filename. + * @throws IOException if the named file exists but is a directory rather + * than a regular file, does not exist but cannot be + * created, or cannot be opened for any other reason + */ + public FileWriter(String fileName) throws IOException { + super(new FileOutputStream(fileName)); + } + + /** + * Constructs a FileWriter object given a file name with a boolean + * indicating whether or not to append the data written. + * + * @param fileName String The system-dependent filename. + * @param append boolean if true, then data will be written + * to the end of the file rather than the beginning. + * @throws IOException if the named file exists but is a directory rather + * than a regular file, does not exist but cannot be + * created, or cannot be opened for any other reason + */ + public FileWriter(String fileName, boolean append) throws IOException { + super(new FileOutputStream(fileName, append)); + } + + /** + * Constructs a FileWriter object given a File object. + * + * @param file a File object to write to. + * @throws IOException if the file exists but is a directory rather than + * a regular file, does not exist but cannot be created, + * or cannot be opened for any other reason + */ + public FileWriter(File file) throws IOException { + super(new FileOutputStream(file)); + } + + /** + * Constructs a FileWriter object given a File object. If the second + * argument is true, then bytes will be written to the end + * of the file rather than the beginning. + * + * @param file a File object to write to + * @param append if true, then bytes will be written + * to the end of the file rather than the beginning + * @throws IOException if the file exists but is a directory rather than + * a regular file, does not exist but cannot be created, + * or cannot be opened for any other reason + * @since 1.4 + */ + public FileWriter(File file, boolean append) throws IOException { + super(new FileOutputStream(file, append)); + } + + /** + * Constructs a FileWriter object associated with a file descriptor. + * + * @param fd FileDescriptor object to write to. + */ + public FileWriter(FileDescriptor fd) { + super(new FileOutputStream(fd)); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/LineNumberInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/LineNumberInputStream.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,292 @@ +/* + * Copyright (c) 1995, 2004, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +/** + * This class is an input stream filter that provides the added + * functionality of keeping track of the current line number. + *

+ * A line is a sequence of bytes ending with a carriage return + * character ('\r'), a newline character + * ('\n'), or a carriage return character followed + * immediately by a linefeed character. In all three cases, the line + * terminating character(s) are returned as a single newline character. + *

+ * The line number begins at 0, and is incremented by + * 1 when a read returns a newline character. + * + * @author Arthur van Hoff + * @see java.io.LineNumberReader + * @since JDK1.0 + * @deprecated This class incorrectly assumes that bytes adequately represent + * characters. As of JDK 1.1, the preferred way to operate on + * character streams is via the new character-stream classes, which + * include a class for counting line numbers. + */ +@Deprecated +public +class LineNumberInputStream extends FilterInputStream { + int pushBack = -1; + int lineNumber; + int markLineNumber; + int markPushBack = -1; + + /** + * Constructs a newline number input stream that reads its input + * from the specified input stream. + * + * @param in the underlying input stream. + */ + public LineNumberInputStream(InputStream in) { + super(in); + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + *

+ * The read method of + * LineNumberInputStream calls the read + * method of the underlying input stream. It checks for carriage + * returns and newline characters in the input, and modifies the + * current line number as appropriate. A carriage-return character or + * a carriage return followed by a newline character are both + * converted into a single newline character. + * + * @return the next byte of data, or -1 if the end of this + * stream is reached. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + * @see java.io.LineNumberInputStream#getLineNumber() + */ + public int read() throws IOException { + int c = pushBack; + + if (c != -1) { + pushBack = -1; + } else { + c = in.read(); + } + + switch (c) { + case '\r': + pushBack = in.read(); + if (pushBack == '\n') { + pushBack = -1; + } + case '\n': + lineNumber++; + return '\n'; + } + return c; + } + + /** + * Reads up to len bytes of data from this input stream + * into an array of bytes. This method blocks until some input is available. + *

+ * The read method of + * LineNumberInputStream repeatedly calls the + * read method of zero arguments to fill in the byte array. + * + * @param b the buffer into which the data is read. + * @param off the start offset of the data. + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * this stream has been reached. + * @exception IOException if an I/O error occurs. + * @see java.io.LineNumberInputStream#read() + */ + public int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int c = read(); + if (c == -1) { + return -1; + } + b[off] = (byte)c; + + int i = 1; + try { + for (; i < len ; i++) { + c = read(); + if (c == -1) { + break; + } + if (b != null) { + b[off + i] = (byte)c; + } + } + } catch (IOException ee) { + } + return i; + } + + /** + * Skips over and discards n bytes of data from this + * input stream. The skip method may, for a variety of + * reasons, end up skipping over some smaller number of bytes, + * possibly 0. The actual number of bytes skipped is + * returned. If n is negative, no bytes are skipped. + *

+ * The skip method of LineNumberInputStream creates + * a byte array and then repeatedly reads into it until + * n bytes have been read or the end of the stream has + * been reached. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public long skip(long n) throws IOException { + int chunk = 2048; + long remaining = n; + byte data[]; + int nr; + + if (n <= 0) { + return 0; + } + + data = new byte[chunk]; + while (remaining > 0) { + nr = read(data, 0, (int) Math.min(chunk, remaining)); + if (nr < 0) { + break; + } + remaining -= nr; + } + + return n - remaining; + } + + /** + * Sets the line number to the specified argument. + * + * @param lineNumber the new line number. + * @see #getLineNumber + */ + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + /** + * Returns the current line number. + * + * @return the current line number. + * @see #setLineNumber + */ + public int getLineNumber() { + return lineNumber; + } + + + /** + * Returns the number of bytes that can be read from this input + * stream without blocking. + *

+ * Note that if the underlying input stream is able to supply + * k input characters without blocking, the + * LineNumberInputStream can guarantee only to provide + * k/2 characters without blocking, because the + * k characters from the underlying input stream might + * consist of k/2 pairs of '\r' and + * '\n', which are converted to just + * k/2 '\n' characters. + * + * @return the number of bytes that can be read from this input stream + * without blocking. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + public int available() throws IOException { + return (pushBack == -1) ? super.available()/2 : super.available()/2 + 1; + } + + /** + * Marks the current position in this input stream. A subsequent + * call to the reset method repositions this stream at + * the last marked position so that subsequent reads re-read the same bytes. + *

+ * The mark method of + * LineNumberInputStream remembers the current line + * number in a private variable, and then calls the mark + * method of the underlying input stream. + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + * @see java.io.FilterInputStream#in + * @see java.io.LineNumberInputStream#reset() + */ + public void mark(int readlimit) { + markLineNumber = lineNumber; + markPushBack = pushBack; + in.mark(readlimit); + } + + /** + * Repositions this stream to the position at the time the + * mark method was last called on this input stream. + *

+ * The reset method of + * LineNumberInputStream resets the line number to be + * the line number at the time the mark method was + * called, and then calls the reset method of the + * underlying input stream. + *

+ * Stream marks are intended to be used in + * situations where you need to read ahead a little to see what's in + * the stream. Often this is most easily done by invoking some + * general parser. If the stream is of the type handled by the + * parser, it just chugs along happily. If the stream is not of + * that type, the parser should toss an exception when it fails, + * which, if it happens within readlimit bytes, allows the outer + * code to reset the stream and try another parser. + * + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + * @see java.io.LineNumberInputStream#mark(int) + */ + public void reset() throws IOException { + lineNumber = markLineNumber; + pushBack = markPushBack; + in.reset(); + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/LineNumberReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/LineNumberReader.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,281 @@ +/* + * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * A buffered character-input stream that keeps track of line numbers. This + * class defines methods {@link #setLineNumber(int)} and {@link + * #getLineNumber()} for setting and getting the current line number + * respectively. + * + *

By default, line numbering begins at 0. This number increments at every + * line terminator as the data is read, and can be changed + * with a call to setLineNumber(int). Note however, that + * setLineNumber(int) does not actually change the current position in + * the stream; it only changes the value that will be returned by + * getLineNumber(). + * + *

A line is considered to be terminated by any one of a + * line feed ('\n'), a carriage return ('\r'), or a carriage return followed + * immediately by a linefeed. + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class LineNumberReader extends BufferedReader { + + /** The current line number */ + private int lineNumber = 0; + + /** The line number of the mark, if any */ + private int markedLineNumber; // Defaults to 0 + + /** If the next character is a line feed, skip it */ + private boolean skipLF; + + /** The skipLF flag when the mark was set */ + private boolean markedSkipLF; + + /** + * Create a new line-numbering reader, using the default input-buffer + * size. + * + * @param in + * A Reader object to provide the underlying stream + */ + public LineNumberReader(Reader in) { + super(in); + } + + /** + * Create a new line-numbering reader, reading characters into a buffer of + * the given size. + * + * @param in + * A Reader object to provide the underlying stream + * + * @param sz + * An int specifying the size of the buffer + */ + public LineNumberReader(Reader in, int sz) { + super(in, sz); + } + + /** + * Set the current line number. + * + * @param lineNumber + * An int specifying the line number + * + * @see #getLineNumber + */ + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + /** + * Get the current line number. + * + * @return The current line number + * + * @see #setLineNumber + */ + public int getLineNumber() { + return lineNumber; + } + + /** + * Read a single character. Line terminators are + * compressed into single newline ('\n') characters. Whenever a line + * terminator is read the current line number is incremented. + * + * @return The character read, or -1 if the end of the stream has been + * reached + * + * @throws IOException + * If an I/O error occurs + */ + public int read() throws IOException { + synchronized (lock) { + int c = super.read(); + if (skipLF) { + if (c == '\n') + c = super.read(); + skipLF = false; + } + switch (c) { + case '\r': + skipLF = true; + case '\n': /* Fall through */ + lineNumber++; + return '\n'; + } + return c; + } + } + + /** + * Read characters into a portion of an array. Whenever a line terminator is read the current line number is + * incremented. + * + * @param cbuf + * Destination buffer + * + * @param off + * Offset at which to start storing characters + * + * @param len + * Maximum number of characters to read + * + * @return The number of bytes read, or -1 if the end of the stream has + * already been reached + * + * @throws IOException + * If an I/O error occurs + */ + public int read(char cbuf[], int off, int len) throws IOException { + synchronized (lock) { + int n = super.read(cbuf, off, len); + + for (int i = off; i < off + n; i++) { + int c = cbuf[i]; + if (skipLF) { + skipLF = false; + if (c == '\n') + continue; + } + switch (c) { + case '\r': + skipLF = true; + case '\n': /* Fall through */ + lineNumber++; + break; + } + } + + return n; + } + } + + /** + * Read a line of text. Whenever a line terminator is + * read the current line number is incremented. + * + * @return A String containing the contents of the line, not including + * any line termination characters, or + * null if the end of the stream has been reached + * + * @throws IOException + * If an I/O error occurs + */ + public String readLine() throws IOException { + synchronized (lock) { + String l = super.readLine(skipLF); + skipLF = false; + if (l != null) + lineNumber++; + return l; + } + } + + /** Maximum skip-buffer size */ + private static final int maxSkipBufferSize = 8192; + + /** Skip buffer, null until allocated */ + private char skipBuffer[] = null; + + /** + * Skip characters. + * + * @param n + * The number of characters to skip + * + * @return The number of characters actually skipped + * + * @throws IOException + * If an I/O error occurs + * + * @throws IllegalArgumentException + * If n is negative + */ + public long skip(long n) throws IOException { + if (n < 0) + throw new IllegalArgumentException("skip() value is negative"); + int nn = (int) Math.min(n, maxSkipBufferSize); + synchronized (lock) { + if ((skipBuffer == null) || (skipBuffer.length < nn)) + skipBuffer = new char[nn]; + long r = n; + while (r > 0) { + int nc = read(skipBuffer, 0, (int) Math.min(r, nn)); + if (nc == -1) + break; + r -= nc; + } + return n - r; + } + } + + /** + * Mark the present position in the stream. Subsequent calls to reset() + * will attempt to reposition the stream to this point, and will also reset + * the line number appropriately. + * + * @param readAheadLimit + * Limit on the number of characters that may be read while still + * preserving the mark. After reading this many characters, + * attempting to reset the stream may fail. + * + * @throws IOException + * If an I/O error occurs + */ + public void mark(int readAheadLimit) throws IOException { + synchronized (lock) { + super.mark(readAheadLimit); + markedLineNumber = lineNumber; + markedSkipLF = skipLF; + } + } + + /** + * Reset the stream to the most recent mark. + * + * @throws IOException + * If the stream has not been marked, or if the mark has been + * invalidated + */ + public void reset() throws IOException { + synchronized (lock) { + super.reset(); + lineNumber = markedLineNumber; + skipLF = markedSkipLF; + } + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/StringReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/StringReader.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,201 @@ +/* + * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * A character stream whose source is a string. + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class StringReader extends Reader { + + private String str; + private int length; + private int next = 0; + private int mark = 0; + + /** + * Creates a new string reader. + * + * @param s String providing the character stream. + */ + public StringReader(String s) { + this.str = s; + this.length = s.length(); + } + + /** Check to make sure that the stream has not been closed */ + private void ensureOpen() throws IOException { + if (str == null) + throw new IOException("Stream closed"); + } + + /** + * Reads a single character. + * + * @return The character read, or -1 if the end of the stream has been + * reached + * + * @exception IOException If an I/O error occurs + */ + public int read() throws IOException { + synchronized (lock) { + ensureOpen(); + if (next >= length) + return -1; + return str.charAt(next++); + } + } + + /** + * Reads characters into a portion of an array. + * + * @param cbuf Destination buffer + * @param off Offset at which to start writing characters + * @param len Maximum number of characters to read + * + * @return The number of characters read, or -1 if the end of the + * stream has been reached + * + * @exception IOException If an I/O error occurs + */ + public int read(char cbuf[], int off, int len) throws IOException { + synchronized (lock) { + ensureOpen(); + if ((off < 0) || (off > cbuf.length) || (len < 0) || + ((off + len) > cbuf.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + if (next >= length) + return -1; + int n = Math.min(length - next, len); + str.getChars(next, next + n, cbuf, off); + next += n; + return n; + } + } + + /** + * Skips the specified number of characters in the stream. Returns + * the number of characters that were skipped. + * + *

The ns parameter may be negative, even though the + * skip method of the {@link Reader} superclass throws + * an exception in this case. Negative values of ns cause the + * stream to skip backwards. Negative return values indicate a skip + * backwards. It is not possible to skip backwards past the beginning of + * the string. + * + *

If the entire string has been read or skipped, then this method has + * no effect and always returns 0. + * + * @exception IOException If an I/O error occurs + */ + public long skip(long ns) throws IOException { + synchronized (lock) { + ensureOpen(); + if (next >= length) + return 0; + // Bound skip by beginning and end of the source + long n = Math.min(length - next, ns); + n = Math.max(-next, n); + next += n; + return n; + } + } + + /** + * Tells whether this stream is ready to be read. + * + * @return True if the next read() is guaranteed not to block for input + * + * @exception IOException If the stream is closed + */ + public boolean ready() throws IOException { + synchronized (lock) { + ensureOpen(); + return true; + } + } + + /** + * Tells whether this stream supports the mark() operation, which it does. + */ + public boolean markSupported() { + return true; + } + + /** + * Marks the present position in the stream. Subsequent calls to reset() + * will reposition the stream to this point. + * + * @param readAheadLimit Limit on the number of characters that may be + * read while still preserving the mark. Because + * the stream's input comes from a string, there + * is no actual limit, so this argument must not + * be negative, but is otherwise ignored. + * + * @exception IllegalArgumentException If readAheadLimit is < 0 + * @exception IOException If an I/O error occurs + */ + public void mark(int readAheadLimit) throws IOException { + if (readAheadLimit < 0){ + throw new IllegalArgumentException("Read-ahead limit < 0"); + } + synchronized (lock) { + ensureOpen(); + mark = next; + } + } + + /** + * Resets the stream to the most recent mark, or to the beginning of the + * string if it has never been marked. + * + * @exception IOException If an I/O error occurs + */ + public void reset() throws IOException { + synchronized (lock) { + ensureOpen(); + next = mark; + } + } + + /** + * Closes the stream and releases any system resources associated with + * it. Once the stream has been closed, further read(), + * ready(), mark(), or reset() invocations will throw an IOException. + * Closing a previously closed stream has no effect. + */ + public void close() { + str = null; + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/StringWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/StringWriter.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,236 @@ +/* + * Copyright (c) 1996, 2004, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + + +/** + * A character stream that collects its output in a string buffer, which can + * then be used to construct a string. + *

+ * Closing a StringWriter has no effect. The methods in this class + * can be called after the stream has been closed without generating an + * IOException. + * + * @author Mark Reinhold + * @since JDK1.1 + */ + +public class StringWriter extends Writer { + + private StringBuffer buf; + + /** + * Create a new string writer using the default initial string-buffer + * size. + */ + public StringWriter() { + buf = new StringBuffer(); + lock = buf; + } + + /** + * Create a new string writer using the specified initial string-buffer + * size. + * + * @param initialSize + * The number of char values that will fit into this buffer + * before it is automatically expanded + * + * @throws IllegalArgumentException + * If initialSize is negative + */ + public StringWriter(int initialSize) { + if (initialSize < 0) { + throw new IllegalArgumentException("Negative buffer size"); + } + buf = new StringBuffer(initialSize); + lock = buf; + } + + /** + * Write a single character. + */ + public void write(int c) { + buf.append((char) c); + } + + /** + * Write a portion of an array of characters. + * + * @param cbuf Array of characters + * @param off Offset from which to start writing characters + * @param len Number of characters to write + */ + public void write(char cbuf[], int off, int len) { + if ((off < 0) || (off > cbuf.length) || (len < 0) || + ((off + len) > cbuf.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + buf.append(cbuf, off, len); + } + + /** + * Write a string. + */ + public void write(String str) { + buf.append(str); + } + + /** + * Write a portion of a string. + * + * @param str String to be written + * @param off Offset from which to start writing characters + * @param len Number of characters to write + */ + public void write(String str, int off, int len) { + buf.append(str.substring(off, off + len)); + } + + /** + * Appends the specified character sequence to this writer. + * + *

An invocation of this method of the form out.append(csq) + * behaves in exactly the same way as the invocation + * + *

+     *     out.write(csq.toString()) 
+ * + *

Depending on the specification of toString for the + * character sequence csq, the entire sequence may not be + * appended. For instance, invoking the toString method of a + * character buffer will return a subsequence whose content depends upon + * the buffer's position and limit. + * + * @param csq + * The character sequence to append. If csq is + * null, then the four characters "null" are + * appended to this writer. + * + * @return This writer + * + * @since 1.5 + */ + public StringWriter append(CharSequence csq) { + if (csq == null) + write("null"); + else + write(csq.toString()); + return this; + } + + /** + * Appends a subsequence of the specified character sequence to this writer. + * + *

An invocation of this method of the form out.append(csq, start, + * end) when csq is not null, behaves in + * exactly the same way as the invocation + * + *

+     *     out.write(csq.subSequence(start, end).toString()) 
+ * + * @param csq + * The character sequence from which a subsequence will be + * appended. If csq is null, then characters + * will be appended as if csq contained the four + * characters "null". + * + * @param start + * The index of the first character in the subsequence + * + * @param end + * The index of the character following the last character in the + * subsequence + * + * @return This writer + * + * @throws IndexOutOfBoundsException + * If start or end are negative, start + * is greater than end, or end is greater than + * csq.length() + * + * @since 1.5 + */ + public StringWriter append(CharSequence csq, int start, int end) { + CharSequence cs = (csq == null ? "null" : csq); + write(cs.subSequence(start, end).toString()); + return this; + } + + /** + * Appends the specified character to this writer. + * + *

An invocation of this method of the form out.append(c) + * behaves in exactly the same way as the invocation + * + *

+     *     out.write(c) 
+ * + * @param c + * The 16-bit character to append + * + * @return This writer + * + * @since 1.5 + */ + public StringWriter append(char c) { + write(c); + return this; + } + + /** + * Return the buffer's current value as a string. + */ + public String toString() { + return buf.toString(); + } + + /** + * Return the string buffer itself. + * + * @return StringBuffer holding the current buffer value. + */ + public StringBuffer getBuffer() { + return buf; + } + + /** + * Flush the stream. + */ + public void flush() { + } + + /** + * Closing a StringWriter has no effect. The methods in this + * class can be called after the stream has been closed without generating + * an IOException. + */ + public void close() throws IOException { + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/io/SyncFailedException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/io/SyncFailedException.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 1996, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +/** + * Signals that a sync operation has failed. + * + * @author Ken Arnold + * @see java.io.FileDescriptor#sync + * @see java.io.IOException + * @since JDK1.1 + */ +public class SyncFailedException extends IOException { + private static final long serialVersionUID = -2353342684412443330L; + + /** + * Constructs an SyncFailedException with a detail message. + * A detail message is a String that describes this particular exception. + * + * @param desc a String describing the exception. + */ + public SyncFailedException(String desc) { + super(desc); + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/nio/charset/Charset.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/Charset.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,923 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.nio.charset; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.spi.CharsetProvider; +import java.security.AccessController; +import java.security.AccessControlException; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.ServiceLoader; +import java.util.ServiceConfigurationError; +import java.util.SortedMap; +import java.util.TreeMap; +import sun.misc.ASCIICaseInsensitiveComparator; +import sun.nio.cs.StandardCharsets; +import sun.nio.cs.ThreadLocalCoders; +import sun.security.action.GetPropertyAction; + + +/** + * A named mapping between sequences of sixteen-bit Unicode code units and sequences of + * bytes. This class defines methods for creating decoders and encoders and + * for retrieving the various names associated with a charset. Instances of + * this class are immutable. + * + *

This class also defines static methods for testing whether a particular + * charset is supported, for locating charset instances by name, and for + * constructing a map that contains every charset for which support is + * available in the current Java virtual machine. Support for new charsets can + * be added via the service-provider interface defined in the {@link + * java.nio.charset.spi.CharsetProvider} class. + * + *

All of the methods defined in this class are safe for use by multiple + * concurrent threads. + * + * + * + *

Charset names

+ * + *

Charsets are named by strings composed of the following characters: + * + *

    + * + *
  • The uppercase letters 'A' through 'Z' + * ('\u0041' through '\u005a'), + * + *
  • The lowercase letters 'a' through 'z' + * ('\u0061' through '\u007a'), + * + *
  • The digits '0' through '9' + * ('\u0030' through '\u0039'), + * + *
  • The dash character '-' + * ('\u002d'HYPHEN-MINUS), + * + *
  • The plus character '+' + * ('\u002b'PLUS SIGN), + * + *
  • The period character '.' + * ('\u002e'FULL STOP), + * + *
  • The colon character ':' + * ('\u003a'COLON), and + * + *
  • The underscore character '_' + * ('\u005f'LOW LINE). + * + *
+ * + * A charset name must begin with either a letter or a digit. The empty string + * is not a legal charset name. Charset names are not case-sensitive; that is, + * case is always ignored when comparing charset names. Charset names + * generally follow the conventions documented in
RFC 2278: IANA Charset + * Registration Procedures. + * + *

Every charset has a canonical name and may also have one or more + * aliases. The canonical name is returned by the {@link #name() name} method + * of this class. Canonical names are, by convention, usually in upper case. + * The aliases of a charset are returned by the {@link #aliases() aliases} + * method. + * + * + * + *

Some charsets have an historical name that is defined for + * compatibility with previous versions of the Java platform. A charset's + * historical name is either its canonical name or one of its aliases. The + * historical name is returned by the getEncoding() methods of the + * {@link java.io.InputStreamReader#getEncoding InputStreamReader} and {@link + * java.io.OutputStreamWriter#getEncoding OutputStreamWriter} classes. + * + * + * + *

If a charset listed in the IANA Charset + * Registry is supported by an implementation of the Java platform then + * its canonical name must be the name listed in the registry. Many charsets + * are given more than one name in the registry, in which case the registry + * identifies one of the names as MIME-preferred. If a charset has more + * than one registry name then its canonical name must be the MIME-preferred + * name and the other names in the registry must be valid aliases. If a + * supported charset is not listed in the IANA registry then its canonical name + * must begin with one of the strings "X-" or "x-". + * + *

The IANA charset registry does change over time, and so the canonical + * name and the aliases of a particular charset may also change over time. To + * ensure compatibility it is recommended that no alias ever be removed from a + * charset, and that if the canonical name of a charset is changed then its + * previous canonical name be made into an alias. + * + * + *

Standard charsets

+ * + * + * + *

Every implementation of the Java platform is required to support the + * following standard charsets. Consult the release documentation for your + * implementation to see if any other charsets are supported. The behavior + * of such optional charsets may differ between implementations. + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *

Charset

Description

US-ASCIISeven-bit ASCII, a.k.a. ISO646-US, + * a.k.a. the Basic Latin block of the Unicode character set
ISO-8859-1  ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1
UTF-8Eight-bit UCS Transformation Format
UTF-16BESixteen-bit UCS Transformation Format, + * big-endian byte order
UTF-16LESixteen-bit UCS Transformation Format, + * little-endian byte order
UTF-16Sixteen-bit UCS Transformation Format, + * byte order identified by an optional byte-order mark
+ * + *

The UTF-8 charset is specified by RFC 2279; the + * transformation format upon which it is based is specified in + * Amendment 2 of ISO 10646-1 and is also described in the Unicode + * Standard. + * + *

The UTF-16 charsets are specified by RFC 2781; the + * transformation formats upon which they are based are specified in + * Amendment 1 of ISO 10646-1 and are also described in the Unicode + * Standard. + * + *

The UTF-16 charsets use sixteen-bit quantities and are + * therefore sensitive to byte order. In these encodings the byte order of a + * stream may be indicated by an initial byte-order mark represented by + * the Unicode character '\uFEFF'. Byte-order marks are handled + * as follows: + * + *

    + * + *
  • When decoding, the UTF-16BE and UTF-16LE + * charsets interpret the initial byte-order marks as a ZERO-WIDTH + * NON-BREAKING SPACE; when encoding, they do not write + * byte-order marks.

  • + + * + *
  • When decoding, the UTF-16 charset interprets the + * byte-order mark at the beginning of the input stream to indicate the + * byte-order of the stream but defaults to big-endian if there is no + * byte-order mark; when encoding, it uses big-endian byte order and writes + * a big-endian byte-order mark.

  • + * + *
+ * + * In any case, byte order marks occuring after the first element of an + * input sequence are not omitted since the same code is used to represent + * ZERO-WIDTH NON-BREAKING SPACE. + * + *

Every instance of the Java virtual machine has a default charset, which + * may or may not be one of the standard charsets. The default charset is + * determined during virtual-machine startup and typically depends upon the + * locale and charset being used by the underlying operating system.

+ * + *

The {@link StandardCharsets} class defines constants for each of the + * standard charsets. + * + *

Terminology

+ * + *

The name of this class is taken from the terms used in + * RFC 2278. + * In that document a charset is defined as the combination of + * one or more coded character sets and a character-encoding scheme. + * (This definition is confusing; some other software systems define + * charset as a synonym for coded character set.) + * + *

A coded character set is a mapping between a set of abstract + * characters and a set of integers. US-ASCII, ISO 8859-1, + * JIS X 0201, and Unicode are examples of coded character sets. + * + *

Some standards have defined a character set to be simply a + * set of abstract characters without an associated assigned numbering. + * An alphabet is an example of such a character set. However, the subtle + * distinction between character set and coded character set + * is rarely used in practice; the former has become a short form for the + * latter, including in the Java API specification. + * + *

A character-encoding scheme is a mapping between one or more + * coded character sets and a set of octet (eight-bit byte) sequences. + * UTF-8, UTF-16, ISO 2022, and EUC are examples of + * character-encoding schemes. Encoding schemes are often associated with + * a particular coded character set; UTF-8, for example, is used only to + * encode Unicode. Some schemes, however, are associated with multiple + * coded character sets; EUC, for example, can be used to encode + * characters in a variety of Asian coded character sets. + * + *

When a coded character set is used exclusively with a single + * character-encoding scheme then the corresponding charset is usually + * named for the coded character set; otherwise a charset is usually named + * for the encoding scheme and, possibly, the locale of the coded + * character sets that it supports. Hence US-ASCII is both the + * name of a coded character set and of the charset that encodes it, while + * EUC-JP is the name of the charset that encodes the + * JIS X 0201, JIS X 0208, and JIS X 0212 + * coded character sets for the Japanese language. + * + *

The native character encoding of the Java programming language is + * UTF-16. A charset in the Java platform therefore defines a mapping + * between sequences of sixteen-bit UTF-16 code units (that is, sequences + * of chars) and sequences of bytes.

+ * + * + * @author Mark Reinhold + * @author JSR-51 Expert Group + * @since 1.4 + * + * @see CharsetDecoder + * @see CharsetEncoder + * @see java.nio.charset.spi.CharsetProvider + * @see java.lang.Character + */ + +public abstract class Charset + implements Comparable +{ + + /* -- Static methods -- */ + + private static volatile String bugLevel = null; + + static boolean atBugLevel(String bl) { // package-private + String level = bugLevel; + if (level == null) { + if (!sun.misc.VM.isBooted()) + return false; + bugLevel = level = AccessController.doPrivileged( + new GetPropertyAction("sun.nio.cs.bugLevel", "")); + } + return level.equals(bl); + } + + /** + * Checks that the given string is a legal charset name.

+ * + * @param s + * A purported charset name + * + * @throws IllegalCharsetNameException + * If the given name is not a legal charset name + */ + private static void checkName(String s) { + int n = s.length(); + if (!atBugLevel("1.4")) { + if (n == 0) + throw new IllegalCharsetNameException(s); + } + for (int i = 0; i < n; i++) { + char c = s.charAt(i); + if (c >= 'A' && c <= 'Z') continue; + if (c >= 'a' && c <= 'z') continue; + if (c >= '0' && c <= '9') continue; + if (c == '-' && i != 0) continue; + if (c == '+' && i != 0) continue; + if (c == ':' && i != 0) continue; + if (c == '_' && i != 0) continue; + if (c == '.' && i != 0) continue; + throw new IllegalCharsetNameException(s); + } + } + + /* The standard set of charsets */ + private static CharsetProvider standardProvider = new StandardCharsets(); + + // Cache of the most-recently-returned charsets, + // along with the names that were used to find them + // + private static volatile Object[] cache1 = null; // "Level 1" cache + private static volatile Object[] cache2 = null; // "Level 2" cache + + private static void cache(String charsetName, Charset cs) { + cache2 = cache1; + cache1 = new Object[] { charsetName, cs }; + } + + // Creates an iterator that walks over the available providers, ignoring + // those whose lookup or instantiation causes a security exception to be + // thrown. Should be invoked with full privileges. + // + private static Iterator providers() { + return new Iterator() { + + ClassLoader cl = ClassLoader.getSystemClassLoader(); + ServiceLoader sl = + ServiceLoader.load(CharsetProvider.class, cl); + Iterator i = sl.iterator(); + + Object next = null; + + private boolean getNext() { + while (next == null) { + try { + if (!i.hasNext()) + return false; + next = i.next(); + } catch (ServiceConfigurationError sce) { + if (sce.getCause() instanceof SecurityException) { + // Ignore security exceptions + continue; + } + throw sce; + } + } + return true; + } + + public boolean hasNext() { + return getNext(); + } + + public Object next() { + if (!getNext()) + throw new NoSuchElementException(); + Object n = next; + next = null; + return n; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + + // Thread-local gate to prevent recursive provider lookups + private static ThreadLocal gate = new ThreadLocal(); + + private static Charset lookupViaProviders(final String charsetName) { + + // The runtime startup sequence looks up standard charsets as a + // consequence of the VM's invocation of System.initializeSystemClass + // in order to, e.g., set system properties and encode filenames. At + // that point the application class loader has not been initialized, + // however, so we can't look for providers because doing so will cause + // that loader to be prematurely initialized with incomplete + // information. + // + if (!sun.misc.VM.isBooted()) + return null; + + if (gate.get() != null) + // Avoid recursive provider lookups + return null; + try { + gate.set(gate); + + return AccessController.doPrivileged( + new PrivilegedAction() { + public Charset run() { + for (Iterator i = providers(); i.hasNext();) { + CharsetProvider cp = (CharsetProvider)i.next(); + Charset cs = cp.charsetForName(charsetName); + if (cs != null) + return cs; + } + return null; + } + }); + + } finally { + gate.set(null); + } + } + + /* The extended set of charsets */ + private static Object extendedProviderLock = new Object(); + private static boolean extendedProviderProbed = false; + private static CharsetProvider extendedProvider = null; + + private static void probeExtendedProvider() { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + try { + Class epc + = Class.forName("sun.nio.cs.ext.ExtendedCharsets"); + extendedProvider = (CharsetProvider)epc.newInstance(); + } catch (ClassNotFoundException x) { + // Extended charsets not available + // (charsets.jar not present) + } catch (InstantiationException x) { + throw new Error(x); + } catch (IllegalAccessException x) { + throw new Error(x); + } + return null; + } + }); + } + + private static Charset lookupExtendedCharset(String charsetName) { + CharsetProvider ecp = null; + synchronized (extendedProviderLock) { + if (!extendedProviderProbed) { + probeExtendedProvider(); + extendedProviderProbed = true; + } + ecp = extendedProvider; + } + return (ecp != null) ? ecp.charsetForName(charsetName) : null; + } + + private static Charset lookup(String charsetName) { + if (charsetName == null) + throw new IllegalArgumentException("Null charset name"); + + Object[] a; + if ((a = cache1) != null && charsetName.equals(a[0])) + return (Charset)a[1]; + // We expect most programs to use one Charset repeatedly. + // We convey a hint to this effect to the VM by putting the + // level 1 cache miss code in a separate method. + return lookup2(charsetName); + } + + private static Charset lookup2(String charsetName) { + Object[] a; + if ((a = cache2) != null && charsetName.equals(a[0])) { + cache2 = cache1; + cache1 = a; + return (Charset)a[1]; + } + + Charset cs; + if ((cs = standardProvider.charsetForName(charsetName)) != null || + (cs = lookupExtendedCharset(charsetName)) != null || + (cs = lookupViaProviders(charsetName)) != null) + { + cache(charsetName, cs); + return cs; + } + + /* Only need to check the name if we didn't find a charset for it */ + checkName(charsetName); + return null; + } + + /** + * Tells whether the named charset is supported.

+ * + * @param charsetName + * The name of the requested charset; may be either + * a canonical name or an alias + * + * @return true if, and only if, support for the named charset + * is available in the current Java virtual machine + * + * @throws IllegalCharsetNameException + * If the given charset name is illegal + * + * @throws IllegalArgumentException + * If the given charsetName is null + */ + public static boolean isSupported(String charsetName) { + return (lookup(charsetName) != null); + } + + /** + * Returns a charset object for the named charset.

+ * + * @param charsetName + * The name of the requested charset; may be either + * a canonical name or an alias + * + * @return A charset object for the named charset + * + * @throws IllegalCharsetNameException + * If the given charset name is illegal + * + * @throws IllegalArgumentException + * If the given charsetName is null + * + * @throws UnsupportedCharsetException + * If no support for the named charset is available + * in this instance of the Java virtual machine + */ + public static Charset forName(String charsetName) { + Charset cs = lookup(charsetName); + if (cs != null) + return cs; + throw new UnsupportedCharsetException(charsetName); + } + + // Fold charsets from the given iterator into the given map, ignoring + // charsets whose names already have entries in the map. + // + private static void put(Iterator i, Map m) { + while (i.hasNext()) { + Charset cs = i.next(); + if (!m.containsKey(cs.name())) + m.put(cs.name(), cs); + } + } + + /** + * Constructs a sorted map from canonical charset names to charset objects. + * + *

The map returned by this method will have one entry for each charset + * for which support is available in the current Java virtual machine. If + * two or more supported charsets have the same canonical name then the + * resulting map will contain just one of them; which one it will contain + * is not specified.

+ * + *

The invocation of this method, and the subsequent use of the + * resulting map, may cause time-consuming disk or network I/O operations + * to occur. This method is provided for applications that need to + * enumerate all of the available charsets, for example to allow user + * charset selection. This method is not used by the {@link #forName + * forName} method, which instead employs an efficient incremental lookup + * algorithm. + * + *

This method may return different results at different times if new + * charset providers are dynamically made available to the current Java + * virtual machine. In the absence of such changes, the charsets returned + * by this method are exactly those that can be retrieved via the {@link + * #forName forName} method.

+ * + * @return An immutable, case-insensitive map from canonical charset names + * to charset objects + */ + public static SortedMap availableCharsets() { + return AccessController.doPrivileged( + new PrivilegedAction>() { + public SortedMap run() { + TreeMap m = + new TreeMap( + ASCIICaseInsensitiveComparator.CASE_INSENSITIVE_ORDER); + put(standardProvider.charsets(), m); + for (Iterator i = providers(); i.hasNext();) { + CharsetProvider cp = (CharsetProvider)i.next(); + put(cp.charsets(), m); + } + return Collections.unmodifiableSortedMap(m); + } + }); + } + + private static volatile Charset defaultCharset; + + /** + * Returns the default charset of this Java virtual machine. + * + *

The default charset is determined during virtual-machine startup and + * typically depends upon the locale and charset of the underlying + * operating system. + * + * @return A charset object for the default charset + * + * @since 1.5 + */ + public static Charset defaultCharset() { + if (defaultCharset == null) { + synchronized (Charset.class) { + String csn = AccessController.doPrivileged( + new GetPropertyAction("file.encoding")); + Charset cs = lookup(csn); + if (cs != null) + defaultCharset = cs; + else + defaultCharset = forName("UTF-8"); + } + } + return defaultCharset; + } + + + /* -- Instance fields and methods -- */ + + private final String name; // tickles a bug in oldjavac + private final String[] aliases; // tickles a bug in oldjavac + private Set aliasSet = null; + + /** + * Initializes a new charset with the given canonical name and alias + * set.

+ * + * @param canonicalName + * The canonical name of this charset + * + * @param aliases + * An array of this charset's aliases, or null if it has no aliases + * + * @throws IllegalCharsetNameException + * If the canonical name or any of the aliases are illegal + */ + protected Charset(String canonicalName, String[] aliases) { + checkName(canonicalName); + String[] as = (aliases == null) ? new String[0] : aliases; + for (int i = 0; i < as.length; i++) + checkName(as[i]); + this.name = canonicalName; + this.aliases = as; + } + + /** + * Returns this charset's canonical name.

+ * + * @return The canonical name of this charset + */ + public final String name() { + return name; + } + + /** + * Returns a set containing this charset's aliases.

+ * + * @return An immutable set of this charset's aliases + */ + public final Set aliases() { + if (aliasSet != null) + return aliasSet; + int n = aliases.length; + HashSet hs = new HashSet(n); + for (int i = 0; i < n; i++) + hs.add(aliases[i]); + aliasSet = Collections.unmodifiableSet(hs); + return aliasSet; + } + + /** + * Returns this charset's human-readable name for the default locale. + * + *

The default implementation of this method simply returns this + * charset's canonical name. Concrete subclasses of this class may + * override this method in order to provide a localized display name.

+ * + * @return The display name of this charset in the default locale + */ + public String displayName() { + return name; + } + + /** + * Tells whether or not this charset is registered in the IANA Charset + * Registry.

+ * + * @return true if, and only if, this charset is known by its + * implementor to be registered with the IANA + */ + public final boolean isRegistered() { + return !name.startsWith("X-") && !name.startsWith("x-"); + } + + /** + * Returns this charset's human-readable name for the given locale. + * + *

The default implementation of this method simply returns this + * charset's canonical name. Concrete subclasses of this class may + * override this method in order to provide a localized display name.

+ * + * @param locale + * The locale for which the display name is to be retrieved + * + * @return The display name of this charset in the given locale + */ + public String displayName(Locale locale) { + return name; + } + + /** + * Tells whether or not this charset contains the given charset. + * + *

A charset C is said to contain a charset D if, + * and only if, every character representable in D is also + * representable in C. If this relationship holds then it is + * guaranteed that every string that can be encoded in D can also be + * encoded in C without performing any replacements. + * + *

That C contains D does not imply that each character + * representable in C by a particular byte sequence is represented + * in D by the same byte sequence, although sometimes this is the + * case. + * + *

Every charset contains itself. + * + *

This method computes an approximation of the containment relation: + * If it returns true then the given charset is known to be + * contained by this charset; if it returns false, however, then + * it is not necessarily the case that the given charset is not contained + * in this charset. + * + * @return true if the given charset is contained in this charset + */ + public abstract boolean contains(Charset cs); + + /** + * Constructs a new decoder for this charset.

+ * + * @return A new decoder for this charset + */ + public abstract CharsetDecoder newDecoder(); + + /** + * Constructs a new encoder for this charset.

+ * + * @return A new encoder for this charset + * + * @throws UnsupportedOperationException + * If this charset does not support encoding + */ + public abstract CharsetEncoder newEncoder(); + + /** + * Tells whether or not this charset supports encoding. + * + *

Nearly all charsets support encoding. The primary exceptions are + * special-purpose auto-detect charsets whose decoders can determine + * which of several possible encoding schemes is in use by examining the + * input byte sequence. Such charsets do not support encoding because + * there is no way to determine which encoding should be used on output. + * Implementations of such charsets should override this method to return + * false.

+ * + * @return true if, and only if, this charset supports encoding + */ + public boolean canEncode() { + return true; + } + + /** + * Convenience method that decodes bytes in this charset into Unicode + * characters. + * + *

An invocation of this method upon a charset cs returns the + * same result as the expression + * + *

+     *     cs.newDecoder()
+     *       .onMalformedInput(CodingErrorAction.REPLACE)
+     *       .onUnmappableCharacter(CodingErrorAction.REPLACE)
+     *       .decode(bb); 
+ * + * except that it is potentially more efficient because it can cache + * decoders between successive invocations. + * + *

This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement byte array. In order + * to detect such sequences, use the {@link + * CharsetDecoder#decode(java.nio.ByteBuffer)} method directly.

+ * + * @param bb The byte buffer to be decoded + * + * @return A char buffer containing the decoded characters + */ + public final CharBuffer decode(ByteBuffer bb) { + try { + return ThreadLocalCoders.decoderFor(this) + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + .decode(bb); + } catch (CharacterCodingException x) { + throw new Error(x); // Can't happen + } + } + + /** + * Convenience method that encodes Unicode characters into bytes in this + * charset. + * + *

An invocation of this method upon a charset cs returns the + * same result as the expression + * + *

+     *     cs.newEncoder()
+     *       .onMalformedInput(CodingErrorAction.REPLACE)
+     *       .onUnmappableCharacter(CodingErrorAction.REPLACE)
+     *       .encode(bb); 
+ * + * except that it is potentially more efficient because it can cache + * encoders between successive invocations. + * + *

This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement string. In order to + * detect such sequences, use the {@link + * CharsetEncoder#encode(java.nio.CharBuffer)} method directly.

+ * + * @param cb The char buffer to be encoded + * + * @return A byte buffer containing the encoded characters + */ + public final ByteBuffer encode(CharBuffer cb) { + try { + return ThreadLocalCoders.encoderFor(this) + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + .encode(cb); + } catch (CharacterCodingException x) { + throw new Error(x); // Can't happen + } + } + + /** + * Convenience method that encodes a string into bytes in this charset. + * + *

An invocation of this method upon a charset cs returns the + * same result as the expression + * + *

+     *     cs.encode(CharBuffer.wrap(s)); 
+ * + * @param str The string to be encoded + * + * @return A byte buffer containing the encoded characters + */ + public final ByteBuffer encode(String str) { + return encode(CharBuffer.wrap(str)); + } + + /** + * Compares this charset to another. + * + *

Charsets are ordered by their canonical names, without regard to + * case.

+ * + * @param that + * The charset to which this charset is to be compared + * + * @return A negative integer, zero, or a positive integer as this charset + * is less than, equal to, or greater than the specified charset + */ + public final int compareTo(Charset that) { + return (name().compareToIgnoreCase(that.name())); + } + + /** + * Computes a hashcode for this charset.

+ * + * @return An integer hashcode + */ + public final int hashCode() { + return name().hashCode(); + } + + /** + * Tells whether or not this object is equal to another. + * + *

Two charsets are equal if, and only if, they have the same canonical + * names. A charset is never equal to any other type of object.

+ * + * @return true if, and only if, this charset is equal to the + * given object + */ + public final boolean equals(Object ob) { + if (!(ob instanceof Charset)) + return false; + if (this == ob) + return true; + return name.equals(((Charset)ob).name()); + } + + /** + * Returns a string describing this charset.

+ * + * @return A string describing this charset + */ + public final String toString() { + return name(); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/nio/charset/CharsetDecoder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/CharsetDecoder.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,972 @@ +/* + * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +// -- This file was mechanically generated: Do not edit! -- // + +package java.nio.charset; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.lang.ref.WeakReference; +import java.nio.charset.CoderMalfunctionError; // javadoc + + +/** + * An engine that can transform a sequence of bytes in a specific charset into a sequence of + * sixteen-bit Unicode characters. + * + * + * + *

The input byte sequence is provided in a byte buffer or a series + * of such buffers. The output character sequence is written to a character buffer + * or a series of such buffers. A decoder should always be used by making + * the following sequence of method invocations, hereinafter referred to as a + * decoding operation: + * + *

    + * + *
  1. Reset the decoder via the {@link #reset reset} method, unless it + * has not been used before;

  2. + * + *
  3. Invoke the {@link #decode decode} method zero or more times, as + * long as additional input may be available, passing false for the + * endOfInput argument and filling the input buffer and flushing the + * output buffer between invocations;

  4. + * + *
  5. Invoke the {@link #decode decode} method one final time, passing + * true for the endOfInput argument; and then

  6. + * + *
  7. Invoke the {@link #flush flush} method so that the decoder can + * flush any internal state to the output buffer.

  8. + * + *
+ * + * Each invocation of the {@link #decode decode} method will decode as many + * bytes as possible from the input buffer, writing the resulting characters + * to the output buffer. The {@link #decode decode} method returns when more + * input is required, when there is not enough room in the output buffer, or + * when a decoding error has occurred. In each case a {@link CoderResult} + * object is returned to describe the reason for termination. An invoker can + * examine this object and fill the input buffer, flush the output buffer, or + * attempt to recover from a decoding error, as appropriate, and try again. + * + *
+ * + *

There are two general types of decoding errors. If the input byte + * sequence is not legal for this charset then the input is considered malformed. If + * the input byte sequence is legal but cannot be mapped to a valid + * Unicode character then an unmappable character has been encountered. + * + * + * + *

How a decoding error is handled depends upon the action requested for + * that type of error, which is described by an instance of the {@link + * CodingErrorAction} class. The possible error actions are to {@link + * CodingErrorAction#IGNORE ignore} the erroneous input, {@link + * CodingErrorAction#REPORT report} the error to the invoker via + * the returned {@link CoderResult} object, or {@link CodingErrorAction#REPLACE + * replace} the erroneous input with the current value of the + * replacement string. The replacement + * + + + + + + * has the initial value "\uFFFD"; + + * + * its value may be changed via the {@link #replaceWith(java.lang.String) + * replaceWith} method. + * + *

The default action for malformed-input and unmappable-character errors + * is to {@link CodingErrorAction#REPORT report} them. The + * malformed-input error action may be changed via the {@link + * #onMalformedInput(CodingErrorAction) onMalformedInput} method; the + * unmappable-character action may be changed via the {@link + * #onUnmappableCharacter(CodingErrorAction) onUnmappableCharacter} method. + * + *

This class is designed to handle many of the details of the decoding + * process, including the implementation of error actions. A decoder for a + * specific charset, which is a concrete subclass of this class, need only + * implement the abstract {@link #decodeLoop decodeLoop} method, which + * encapsulates the basic decoding loop. A subclass that maintains internal + * state should, additionally, override the {@link #implFlush implFlush} and + * {@link #implReset implReset} methods. + * + *

Instances of this class are not safe for use by multiple concurrent + * threads.

+ * + * + * @author Mark Reinhold + * @author JSR-51 Expert Group + * @since 1.4 + * + * @see ByteBuffer + * @see CharBuffer + * @see Charset + * @see CharsetEncoder + */ + +public abstract class CharsetDecoder { + + private final Charset charset; + private final float averageCharsPerByte; + private final float maxCharsPerByte; + + private String replacement; + private CodingErrorAction malformedInputAction + = CodingErrorAction.REPORT; + private CodingErrorAction unmappableCharacterAction + = CodingErrorAction.REPORT; + + // Internal states + // + private static final int ST_RESET = 0; + private static final int ST_CODING = 1; + private static final int ST_END = 2; + private static final int ST_FLUSHED = 3; + + private int state = ST_RESET; + + private static String stateNames[] + = { "RESET", "CODING", "CODING_END", "FLUSHED" }; + + + /** + * Initializes a new decoder. The new decoder will have the given + * chars-per-byte and replacement values.

+ * + * @param averageCharsPerByte + * A positive float value indicating the expected number of + * characters that will be produced for each input byte + * + * @param maxCharsPerByte + * A positive float value indicating the maximum number of + * characters that will be produced for each input byte + * + * @param replacement + * The initial replacement; must not be null, must have + * non-zero length, must not be longer than maxCharsPerByte, + * and must be {@link #isLegalReplacement
legal} + * + * @throws IllegalArgumentException + * If the preconditions on the parameters do not hold + */ + private + CharsetDecoder(Charset cs, + float averageCharsPerByte, + float maxCharsPerByte, + String replacement) + { + this.charset = cs; + if (averageCharsPerByte <= 0.0f) + throw new IllegalArgumentException("Non-positive " + + "averageCharsPerByte"); + if (maxCharsPerByte <= 0.0f) + throw new IllegalArgumentException("Non-positive " + + "maxCharsPerByte"); + if (!Charset.atBugLevel("1.4")) { + if (averageCharsPerByte > maxCharsPerByte) + throw new IllegalArgumentException("averageCharsPerByte" + + " exceeds " + + "maxCharsPerByte"); + } + this.replacement = replacement; + this.averageCharsPerByte = averageCharsPerByte; + this.maxCharsPerByte = maxCharsPerByte; + replaceWith(replacement); + } + + /** + * Initializes a new decoder. The new decoder will have the given + * chars-per-byte values and its replacement will be the + * string "\uFFFD".

+ * + * @param averageCharsPerByte + * A positive float value indicating the expected number of + * characters that will be produced for each input byte + * + * @param maxCharsPerByte + * A positive float value indicating the maximum number of + * characters that will be produced for each input byte + * + * @throws IllegalArgumentException + * If the preconditions on the parameters do not hold + */ + protected CharsetDecoder(Charset cs, + float averageCharsPerByte, + float maxCharsPerByte) + { + this(cs, + averageCharsPerByte, maxCharsPerByte, + "\uFFFD"); + } + + /** + * Returns the charset that created this decoder.

+ * + * @return This decoder's charset + */ + public final Charset charset() { + return charset; + } + + /** + * Returns this decoder's replacement value.

+ * + * @return This decoder's current replacement, + * which is never null and is never empty + */ + public final String replacement() { + return replacement; + } + + /** + * Changes this decoder's replacement value. + * + *

This method invokes the {@link #implReplaceWith implReplaceWith} + * method, passing the new replacement, after checking that the new + * replacement is acceptable.

+ * + * @param newReplacement + * + + * The new replacement; must not be null + * and must have non-zero length + + + + + + + + * + * @return This decoder + * + * @throws IllegalArgumentException + * If the preconditions on the parameter do not hold + */ + public final CharsetDecoder replaceWith(String newReplacement) { + if (newReplacement == null) + throw new IllegalArgumentException("Null replacement"); + int len = newReplacement.length(); + if (len == 0) + throw new IllegalArgumentException("Empty replacement"); + if (len > maxCharsPerByte) + throw new IllegalArgumentException("Replacement too long"); + + + + + this.replacement = newReplacement; + implReplaceWith(newReplacement); + return this; + } + + /** + * Reports a change to this decoder's replacement value. + * + *

The default implementation of this method does nothing. This method + * should be overridden by decoders that require notification of changes to + * the replacement.

+ * + * @param newReplacement + */ + protected void implReplaceWith(String newReplacement) { + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /** + * Returns this decoder's current action for malformed-input errors.

+ * + * @return The current malformed-input action, which is never null + */ + public CodingErrorAction malformedInputAction() { + return malformedInputAction; + } + + /** + * Changes this decoder's action for malformed-input errors.

+ * + *

This method invokes the {@link #implOnMalformedInput + * implOnMalformedInput} method, passing the new action.

+ * + * @param newAction The new action; must not be null + * + * @return This decoder + * + * @throws IllegalArgumentException + * If the precondition on the parameter does not hold + */ + public final CharsetDecoder onMalformedInput(CodingErrorAction newAction) { + if (newAction == null) + throw new IllegalArgumentException("Null action"); + malformedInputAction = newAction; + implOnMalformedInput(newAction); + return this; + } + + /** + * Reports a change to this decoder's malformed-input action. + * + *

The default implementation of this method does nothing. This method + * should be overridden by decoders that require notification of changes to + * the malformed-input action.

+ */ + protected void implOnMalformedInput(CodingErrorAction newAction) { } + + /** + * Returns this decoder's current action for unmappable-character errors. + *

+ * + * @return The current unmappable-character action, which is never + * null + */ + public CodingErrorAction unmappableCharacterAction() { + return unmappableCharacterAction; + } + + /** + * Changes this decoder's action for unmappable-character errors. + * + *

This method invokes the {@link #implOnUnmappableCharacter + * implOnUnmappableCharacter} method, passing the new action.

+ * + * @param newAction The new action; must not be null + * + * @return This decoder + * + * @throws IllegalArgumentException + * If the precondition on the parameter does not hold + */ + public final CharsetDecoder onUnmappableCharacter(CodingErrorAction + newAction) + { + if (newAction == null) + throw new IllegalArgumentException("Null action"); + unmappableCharacterAction = newAction; + implOnUnmappableCharacter(newAction); + return this; + } + + /** + * Reports a change to this decoder's unmappable-character action. + * + *

The default implementation of this method does nothing. This method + * should be overridden by decoders that require notification of changes to + * the unmappable-character action.

+ */ + protected void implOnUnmappableCharacter(CodingErrorAction newAction) { } + + /** + * Returns the average number of characters that will be produced for each + * byte of input. This heuristic value may be used to estimate the size + * of the output buffer required for a given input sequence.

+ * + * @return The average number of characters produced + * per byte of input + */ + public final float averageCharsPerByte() { + return averageCharsPerByte; + } + + /** + * Returns the maximum number of characters that will be produced for each + * byte of input. This value may be used to compute the worst-case size + * of the output buffer required for a given input sequence.

+ * + * @return The maximum number of characters that will be produced per + * byte of input + */ + public final float maxCharsPerByte() { + return maxCharsPerByte; + } + + /** + * Decodes as many bytes as possible from the given input buffer, + * writing the results to the given output buffer. + * + *

The buffers are read from, and written to, starting at their current + * positions. At most {@link Buffer#remaining in.remaining()} bytes + * will be read and at most {@link Buffer#remaining out.remaining()} + * characters will be written. The buffers' positions will be advanced to + * reflect the bytes read and the characters written, but their marks and + * limits will not be modified. + * + *

In addition to reading bytes from the input buffer and writing + * characters to the output buffer, this method returns a {@link CoderResult} + * object to describe its reason for termination: + * + *

    + * + *
  • {@link CoderResult#UNDERFLOW} indicates that as much of the + * input buffer as possible has been decoded. If there is no further + * input then the invoker can proceed to the next step of the + * decoding operation. Otherwise this method + * should be invoked again with further input.

  • + * + *
  • {@link CoderResult#OVERFLOW} indicates that there is + * insufficient space in the output buffer to decode any more bytes. + * This method should be invoked again with an output buffer that has + * more {@linkplain Buffer#remaining remaining} characters. This is + * typically done by draining any decoded characters from the output + * buffer.

  • + * + *
  • A {@link CoderResult#malformedForLength + * malformed-input} result indicates that a malformed-input + * error has been detected. The malformed bytes begin at the input + * buffer's (possibly incremented) position; the number of malformed + * bytes may be determined by invoking the result object's {@link + * CoderResult#length() length} method. This case applies only if the + * {@link #onMalformedInput malformed action} of this decoder + * is {@link CodingErrorAction#REPORT}; otherwise the malformed input + * will be ignored or replaced, as requested.

  • + * + *
  • An {@link CoderResult#unmappableForLength + * unmappable-character} result indicates that an + * unmappable-character error has been detected. The bytes that + * decode the unmappable character begin at the input buffer's (possibly + * incremented) position; the number of such bytes may be determined + * by invoking the result object's {@link CoderResult#length() length} + * method. This case applies only if the {@link #onUnmappableCharacter + * unmappable action} of this decoder is {@link + * CodingErrorAction#REPORT}; otherwise the unmappable character will be + * ignored or replaced, as requested.

  • + * + *
+ * + * In any case, if this method is to be reinvoked in the same decoding + * operation then care should be taken to preserve any bytes remaining + * in the input buffer so that they are available to the next invocation. + * + *

The endOfInput parameter advises this method as to whether + * the invoker can provide further input beyond that contained in the given + * input buffer. If there is a possibility of providing additional input + * then the invoker should pass false for this parameter; if there + * is no possibility of providing further input then the invoker should + * pass true. It is not erroneous, and in fact it is quite + * common, to pass false in one invocation and later discover that + * no further input was actually available. It is critical, however, that + * the final invocation of this method in a sequence of invocations always + * pass true so that any remaining undecoded input will be treated + * as being malformed. + * + *

This method works by invoking the {@link #decodeLoop decodeLoop} + * method, interpreting its results, handling error conditions, and + * reinvoking it as necessary.

+ * + * + * @param in + * The input byte buffer + * + * @param out + * The output character buffer + * + * @param endOfInput + * true if, and only if, the invoker can provide no + * additional input bytes beyond those in the given buffer + * + * @return A coder-result object describing the reason for termination + * + * @throws IllegalStateException + * If a decoding operation is already in progress and the previous + * step was an invocation neither of the {@link #reset reset} + * method, nor of this method with a value of false for + * the endOfInput parameter, nor of this method with a + * value of true for the endOfInput parameter + * but a return value indicating an incomplete decoding operation + * + * @throws CoderMalfunctionError + * If an invocation of the decodeLoop method threw + * an unexpected exception + */ + public final CoderResult decode(ByteBuffer in, CharBuffer out, + boolean endOfInput) + { + int newState = endOfInput ? ST_END : ST_CODING; + if ((state != ST_RESET) && (state != ST_CODING) + && !(endOfInput && (state == ST_END))) + throwIllegalStateException(state, newState); + state = newState; + + for (;;) { + + CoderResult cr; + try { + cr = decodeLoop(in, out); + } catch (BufferUnderflowException x) { + throw new CoderMalfunctionError(x); + } catch (BufferOverflowException x) { + throw new CoderMalfunctionError(x); + } + + if (cr.isOverflow()) + return cr; + + if (cr.isUnderflow()) { + if (endOfInput && in.hasRemaining()) { + cr = CoderResult.malformedForLength(in.remaining()); + // Fall through to malformed-input case + } else { + return cr; + } + } + + CodingErrorAction action = null; + if (cr.isMalformed()) + action = malformedInputAction; + else if (cr.isUnmappable()) + action = unmappableCharacterAction; + else + assert false : cr.toString(); + + if (action == CodingErrorAction.REPORT) + return cr; + + if (action == CodingErrorAction.REPLACE) { + if (out.remaining() < replacement.length()) + return CoderResult.OVERFLOW; + out.put(replacement); + } + + if ((action == CodingErrorAction.IGNORE) + || (action == CodingErrorAction.REPLACE)) { + // Skip erroneous input either way + in.position(in.position() + cr.length()); + continue; + } + + assert false; + } + + } + + /** + * Flushes this decoder. + * + *

Some decoders maintain internal state and may need to write some + * final characters to the output buffer once the overall input sequence has + * been read. + * + *

Any additional output is written to the output buffer beginning at + * its current position. At most {@link Buffer#remaining out.remaining()} + * characters will be written. The buffer's position will be advanced + * appropriately, but its mark and limit will not be modified. + * + *

If this method completes successfully then it returns {@link + * CoderResult#UNDERFLOW}. If there is insufficient room in the output + * buffer then it returns {@link CoderResult#OVERFLOW}. If this happens + * then this method must be invoked again, with an output buffer that has + * more room, in order to complete the current decoding + * operation. + * + *

If this decoder has already been flushed then invoking this method + * has no effect. + * + *

This method invokes the {@link #implFlush implFlush} method to + * perform the actual flushing operation.

+ * + * @param out + * The output character buffer + * + * @return A coder-result object, either {@link CoderResult#UNDERFLOW} or + * {@link CoderResult#OVERFLOW} + * + * @throws IllegalStateException + * If the previous step of the current decoding operation was an + * invocation neither of the {@link #flush flush} method nor of + * the three-argument {@link + * #decode(ByteBuffer,CharBuffer,boolean) decode} method + * with a value of true for the endOfInput + * parameter + */ + public final CoderResult flush(CharBuffer out) { + if (state == ST_END) { + CoderResult cr = implFlush(out); + if (cr.isUnderflow()) + state = ST_FLUSHED; + return cr; + } + + if (state != ST_FLUSHED) + throwIllegalStateException(state, ST_FLUSHED); + + return CoderResult.UNDERFLOW; // Already flushed + } + + /** + * Flushes this decoder. + * + *

The default implementation of this method does nothing, and always + * returns {@link CoderResult#UNDERFLOW}. This method should be overridden + * by decoders that may need to write final characters to the output buffer + * once the entire input sequence has been read.

+ * + * @param out + * The output character buffer + * + * @return A coder-result object, either {@link CoderResult#UNDERFLOW} or + * {@link CoderResult#OVERFLOW} + */ + protected CoderResult implFlush(CharBuffer out) { + return CoderResult.UNDERFLOW; + } + + /** + * Resets this decoder, clearing any internal state. + * + *

This method resets charset-independent state and also invokes the + * {@link #implReset() implReset} method in order to perform any + * charset-specific reset actions.

+ * + * @return This decoder + * + */ + public final CharsetDecoder reset() { + implReset(); + state = ST_RESET; + return this; + } + + /** + * Resets this decoder, clearing any charset-specific internal state. + * + *

The default implementation of this method does nothing. This method + * should be overridden by decoders that maintain internal state.

+ */ + protected void implReset() { } + + /** + * Decodes one or more bytes into one or more characters. + * + *

This method encapsulates the basic decoding loop, decoding as many + * bytes as possible until it either runs out of input, runs out of room + * in the output buffer, or encounters a decoding error. This method is + * invoked by the {@link #decode decode} method, which handles result + * interpretation and error recovery. + * + *

The buffers are read from, and written to, starting at their current + * positions. At most {@link Buffer#remaining in.remaining()} bytes + * will be read, and at most {@link Buffer#remaining out.remaining()} + * characters will be written. The buffers' positions will be advanced to + * reflect the bytes read and the characters written, but their marks and + * limits will not be modified. + * + *

This method returns a {@link CoderResult} object to describe its + * reason for termination, in the same manner as the {@link #decode decode} + * method. Most implementations of this method will handle decoding errors + * by returning an appropriate result object for interpretation by the + * {@link #decode decode} method. An optimized implementation may instead + * examine the relevant error action and implement that action itself. + * + *

An implementation of this method may perform arbitrary lookahead by + * returning {@link CoderResult#UNDERFLOW} until it receives sufficient + * input.

+ * + * @param in + * The input byte buffer + * + * @param out + * The output character buffer + * + * @return A coder-result object describing the reason for termination + */ + protected abstract CoderResult decodeLoop(ByteBuffer in, + CharBuffer out); + + /** + * Convenience method that decodes the remaining content of a single input + * byte buffer into a newly-allocated character buffer. + * + *

This method implements an entire decoding + * operation; that is, it resets this decoder, then it decodes the + * bytes in the given byte buffer, and finally it flushes this + * decoder. This method should therefore not be invoked if a decoding + * operation is already in progress.

+ * + * @param in + * The input byte buffer + * + * @return A newly-allocated character buffer containing the result of the + * decoding operation. The buffer's position will be zero and its + * limit will follow the last character written. + * + * @throws IllegalStateException + * If a decoding operation is already in progress + * + * @throws MalformedInputException + * If the byte sequence starting at the input buffer's current + * position is not legal for this charset and the current malformed-input action + * is {@link CodingErrorAction#REPORT} + * + * @throws UnmappableCharacterException + * If the byte sequence starting at the input buffer's current + * position cannot be mapped to an equivalent character sequence and + * the current unmappable-character action is {@link + * CodingErrorAction#REPORT} + */ + public final CharBuffer decode(ByteBuffer in) + throws CharacterCodingException + { + int n = (int)(in.remaining() * averageCharsPerByte()); + CharBuffer out = CharBuffer.allocate(n); + + if ((n == 0) && (in.remaining() == 0)) + return out; + reset(); + for (;;) { + CoderResult cr = in.hasRemaining() ? + decode(in, out, true) : CoderResult.UNDERFLOW; + if (cr.isUnderflow()) + cr = flush(out); + + if (cr.isUnderflow()) + break; + if (cr.isOverflow()) { + n = 2*n + 1; // Ensure progress; n might be 0! + CharBuffer o = CharBuffer.allocate(n); + out.flip(); + o.put(out); + out = o; + continue; + } + cr.throwException(); + } + out.flip(); + return out; + } + + + + /** + * Tells whether or not this decoder implements an auto-detecting charset. + * + *

The default implementation of this method always returns + * false; it should be overridden by auto-detecting decoders to + * return true.

+ * + * @return true if, and only if, this decoder implements an + * auto-detecting charset + */ + public boolean isAutoDetecting() { + return false; + } + + /** + * Tells whether or not this decoder has yet detected a + * charset  (optional operation). + * + *

If this decoder implements an auto-detecting charset then at a + * single point during a decoding operation this method may start returning + * true to indicate that a specific charset has been detected in + * the input byte sequence. Once this occurs, the {@link #detectedCharset + * detectedCharset} method may be invoked to retrieve the detected charset. + * + *

That this method returns false does not imply that no bytes + * have yet been decoded. Some auto-detecting decoders are capable of + * decoding some, or even all, of an input byte sequence without fixing on + * a particular charset. + * + *

The default implementation of this method always throws an {@link + * UnsupportedOperationException}; it should be overridden by + * auto-detecting decoders to return true once the input charset + * has been determined.

+ * + * @return true if, and only if, this decoder has detected a + * specific charset + * + * @throws UnsupportedOperationException + * If this decoder does not implement an auto-detecting charset + */ + public boolean isCharsetDetected() { + throw new UnsupportedOperationException(); + } + + /** + * Retrieves the charset that was detected by this + * decoder  (optional operation). + * + *

If this decoder implements an auto-detecting charset then this + * method returns the actual charset once it has been detected. After that + * point, this method returns the same value for the duration of the + * current decoding operation. If not enough input bytes have yet been + * read to determine the actual charset then this method throws an {@link + * IllegalStateException}. + * + *

The default implementation of this method always throws an {@link + * UnsupportedOperationException}; it should be overridden by + * auto-detecting decoders to return the appropriate value.

+ * + * @return The charset detected by this auto-detecting decoder, + * or null if the charset has not yet been determined + * + * @throws IllegalStateException + * If insufficient bytes have been read to determine a charset + * + * @throws UnsupportedOperationException + * If this decoder does not implement an auto-detecting charset + */ + public Charset detectedCharset() { + throw new UnsupportedOperationException(); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + private void throwIllegalStateException(int from, int to) { + throw new IllegalStateException("Current state = " + stateNames[from] + + ", new state = " + stateNames[to]); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/nio/charset/CharsetEncoder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/CharsetEncoder.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,972 @@ +/* + * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +// -- This file was mechanically generated: Do not edit! -- // + +package java.nio.charset; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.lang.ref.WeakReference; +import java.nio.charset.CoderMalfunctionError; // javadoc + + +/** + * An engine that can transform a sequence of sixteen-bit Unicode characters into a sequence of + * bytes in a specific charset. + * + * + * + *

The input character sequence is provided in a character buffer or a series + * of such buffers. The output byte sequence is written to a byte buffer + * or a series of such buffers. An encoder should always be used by making + * the following sequence of method invocations, hereinafter referred to as an + * encoding operation: + * + *

    + * + *
  1. Reset the encoder via the {@link #reset reset} method, unless it + * has not been used before;

  2. + * + *
  3. Invoke the {@link #encode encode} method zero or more times, as + * long as additional input may be available, passing false for the + * endOfInput argument and filling the input buffer and flushing the + * output buffer between invocations;

  4. + * + *
  5. Invoke the {@link #encode encode} method one final time, passing + * true for the endOfInput argument; and then

  6. + * + *
  7. Invoke the {@link #flush flush} method so that the encoder can + * flush any internal state to the output buffer.

  8. + * + *
+ * + * Each invocation of the {@link #encode encode} method will encode as many + * characters as possible from the input buffer, writing the resulting bytes + * to the output buffer. The {@link #encode encode} method returns when more + * input is required, when there is not enough room in the output buffer, or + * when an encoding error has occurred. In each case a {@link CoderResult} + * object is returned to describe the reason for termination. An invoker can + * examine this object and fill the input buffer, flush the output buffer, or + * attempt to recover from an encoding error, as appropriate, and try again. + * + *
+ * + *

There are two general types of encoding errors. If the input character + * sequence is not a legal sixteen-bit Unicode sequence then the input is considered malformed. If + * the input character sequence is legal but cannot be mapped to a valid + * byte sequence in the given charset then an unmappable character has been encountered. + * + * + * + *

How an encoding error is handled depends upon the action requested for + * that type of error, which is described by an instance of the {@link + * CodingErrorAction} class. The possible error actions are to {@link + * CodingErrorAction#IGNORE ignore} the erroneous input, {@link + * CodingErrorAction#REPORT report} the error to the invoker via + * the returned {@link CoderResult} object, or {@link CodingErrorAction#REPLACE + * replace} the erroneous input with the current value of the + * replacement byte array. The replacement + * + + * is initially set to the encoder's default replacement, which often + * (but not always) has the initial value { (byte)'?' }; + + + + + * + * its value may be changed via the {@link #replaceWith(byte[]) + * replaceWith} method. + * + *

The default action for malformed-input and unmappable-character errors + * is to {@link CodingErrorAction#REPORT report} them. The + * malformed-input error action may be changed via the {@link + * #onMalformedInput(CodingErrorAction) onMalformedInput} method; the + * unmappable-character action may be changed via the {@link + * #onUnmappableCharacter(CodingErrorAction) onUnmappableCharacter} method. + * + *

This class is designed to handle many of the details of the encoding + * process, including the implementation of error actions. An encoder for a + * specific charset, which is a concrete subclass of this class, need only + * implement the abstract {@link #encodeLoop encodeLoop} method, which + * encapsulates the basic encoding loop. A subclass that maintains internal + * state should, additionally, override the {@link #implFlush implFlush} and + * {@link #implReset implReset} methods. + * + *

Instances of this class are not safe for use by multiple concurrent + * threads.

+ * + * + * @author Mark Reinhold + * @author JSR-51 Expert Group + * @since 1.4 + * + * @see ByteBuffer + * @see CharBuffer + * @see Charset + * @see CharsetDecoder + */ + +public abstract class CharsetEncoder { + + private final Charset charset; + private final float averageBytesPerChar; + private final float maxBytesPerChar; + + private byte[] replacement; + private CodingErrorAction malformedInputAction + = CodingErrorAction.REPORT; + private CodingErrorAction unmappableCharacterAction + = CodingErrorAction.REPORT; + + // Internal states + // + private static final int ST_RESET = 0; + private static final int ST_CODING = 1; + private static final int ST_END = 2; + private static final int ST_FLUSHED = 3; + + private int state = ST_RESET; + + private static String stateNames[] + = { "RESET", "CODING", "CODING_END", "FLUSHED" }; + + + /** + * Initializes a new encoder. The new encoder will have the given + * bytes-per-char and replacement values.

+ * + * @param averageBytesPerChar + * A positive float value indicating the expected number of + * bytes that will be produced for each input character + * + * @param maxBytesPerChar + * A positive float value indicating the maximum number of + * bytes that will be produced for each input character + * + * @param replacement + * The initial replacement; must not be null, must have + * non-zero length, must not be longer than maxBytesPerChar, + * and must be {@link #isLegalReplacement
legal} + * + * @throws IllegalArgumentException + * If the preconditions on the parameters do not hold + */ + protected + CharsetEncoder(Charset cs, + float averageBytesPerChar, + float maxBytesPerChar, + byte[] replacement) + { + this.charset = cs; + if (averageBytesPerChar <= 0.0f) + throw new IllegalArgumentException("Non-positive " + + "averageBytesPerChar"); + if (maxBytesPerChar <= 0.0f) + throw new IllegalArgumentException("Non-positive " + + "maxBytesPerChar"); + if (!Charset.atBugLevel("1.4")) { + if (averageBytesPerChar > maxBytesPerChar) + throw new IllegalArgumentException("averageBytesPerChar" + + " exceeds " + + "maxBytesPerChar"); + } + this.replacement = replacement; + this.averageBytesPerChar = averageBytesPerChar; + this.maxBytesPerChar = maxBytesPerChar; + replaceWith(replacement); + } + + /** + * Initializes a new encoder. The new encoder will have the given + * bytes-per-char values and its replacement will be the + * byte array { (byte)'?' }.

+ * + * @param averageBytesPerChar + * A positive float value indicating the expected number of + * bytes that will be produced for each input character + * + * @param maxBytesPerChar + * A positive float value indicating the maximum number of + * bytes that will be produced for each input character + * + * @throws IllegalArgumentException + * If the preconditions on the parameters do not hold + */ + protected CharsetEncoder(Charset cs, + float averageBytesPerChar, + float maxBytesPerChar) + { + this(cs, + averageBytesPerChar, maxBytesPerChar, + new byte[] { (byte)'?' }); + } + + /** + * Returns the charset that created this encoder.

+ * + * @return This encoder's charset + */ + public final Charset charset() { + return charset; + } + + /** + * Returns this encoder's replacement value.

+ * + * @return This encoder's current replacement, + * which is never null and is never empty + */ + public final byte[] replacement() { + return replacement; + } + + /** + * Changes this encoder's replacement value. + * + *

This method invokes the {@link #implReplaceWith implReplaceWith} + * method, passing the new replacement, after checking that the new + * replacement is acceptable.

+ * + * @param newReplacement + * + + + + + + * The new replacement; must not be null, must have + * non-zero length, must not be longer than the value returned by + * the {@link #maxBytesPerChar() maxBytesPerChar} method, and + * must be {@link #isLegalReplacement
legal} + + * + * @return This encoder + * + * @throws IllegalArgumentException + * If the preconditions on the parameter do not hold + */ + public final CharsetEncoder replaceWith(byte[] newReplacement) { + if (newReplacement == null) + throw new IllegalArgumentException("Null replacement"); + int len = newReplacement.length; + if (len == 0) + throw new IllegalArgumentException("Empty replacement"); + if (len > maxBytesPerChar) + throw new IllegalArgumentException("Replacement too long"); + + if (!isLegalReplacement(newReplacement)) + throw new IllegalArgumentException("Illegal replacement"); + + this.replacement = newReplacement; + implReplaceWith(newReplacement); + return this; + } + + /** + * Reports a change to this encoder's replacement value. + * + *

The default implementation of this method does nothing. This method + * should be overridden by encoders that require notification of changes to + * the replacement.

+ * + * @param newReplacement + */ + protected void implReplaceWith(byte[] newReplacement) { + } + + + + private WeakReference cachedDecoder = null; + + /** + * Tells whether or not the given byte array is a legal replacement value + * for this encoder. + * + *

A replacement is legal if, and only if, it is a legal sequence of + * bytes in this encoder's charset; that is, it must be possible to decode + * the replacement into one or more sixteen-bit Unicode characters. + * + *

The default implementation of this method is not very efficient; it + * should generally be overridden to improve performance.

+ * + * @param repl The byte array to be tested + * + * @return true if, and only if, the given byte array + * is a legal replacement value for this encoder + */ + public boolean isLegalReplacement(byte[] repl) { + WeakReference wr = cachedDecoder; + CharsetDecoder dec = null; + if ((wr == null) || ((dec = wr.get()) == null)) { + dec = charset().newDecoder(); + dec.onMalformedInput(CodingErrorAction.REPORT); + dec.onUnmappableCharacter(CodingErrorAction.REPORT); + cachedDecoder = new WeakReference(dec); + } else { + dec.reset(); + } + ByteBuffer bb = ByteBuffer.wrap(repl); + CharBuffer cb = CharBuffer.allocate((int)(bb.remaining() + * dec.maxCharsPerByte())); + CoderResult cr = dec.decode(bb, cb, true); + return !cr.isError(); + } + + + + /** + * Returns this encoder's current action for malformed-input errors.

+ * + * @return The current malformed-input action, which is never null + */ + public CodingErrorAction malformedInputAction() { + return malformedInputAction; + } + + /** + * Changes this encoder's action for malformed-input errors.

+ * + *

This method invokes the {@link #implOnMalformedInput + * implOnMalformedInput} method, passing the new action.

+ * + * @param newAction The new action; must not be null + * + * @return This encoder + * + * @throws IllegalArgumentException + * If the precondition on the parameter does not hold + */ + public final CharsetEncoder onMalformedInput(CodingErrorAction newAction) { + if (newAction == null) + throw new IllegalArgumentException("Null action"); + malformedInputAction = newAction; + implOnMalformedInput(newAction); + return this; + } + + /** + * Reports a change to this encoder's malformed-input action. + * + *

The default implementation of this method does nothing. This method + * should be overridden by encoders that require notification of changes to + * the malformed-input action.

+ */ + protected void implOnMalformedInput(CodingErrorAction newAction) { } + + /** + * Returns this encoder's current action for unmappable-character errors. + *

+ * + * @return The current unmappable-character action, which is never + * null + */ + public CodingErrorAction unmappableCharacterAction() { + return unmappableCharacterAction; + } + + /** + * Changes this encoder's action for unmappable-character errors. + * + *

This method invokes the {@link #implOnUnmappableCharacter + * implOnUnmappableCharacter} method, passing the new action.

+ * + * @param newAction The new action; must not be null + * + * @return This encoder + * + * @throws IllegalArgumentException + * If the precondition on the parameter does not hold + */ + public final CharsetEncoder onUnmappableCharacter(CodingErrorAction + newAction) + { + if (newAction == null) + throw new IllegalArgumentException("Null action"); + unmappableCharacterAction = newAction; + implOnUnmappableCharacter(newAction); + return this; + } + + /** + * Reports a change to this encoder's unmappable-character action. + * + *

The default implementation of this method does nothing. This method + * should be overridden by encoders that require notification of changes to + * the unmappable-character action.

+ */ + protected void implOnUnmappableCharacter(CodingErrorAction newAction) { } + + /** + * Returns the average number of bytes that will be produced for each + * character of input. This heuristic value may be used to estimate the size + * of the output buffer required for a given input sequence.

+ * + * @return The average number of bytes produced + * per character of input + */ + public final float averageBytesPerChar() { + return averageBytesPerChar; + } + + /** + * Returns the maximum number of bytes that will be produced for each + * character of input. This value may be used to compute the worst-case size + * of the output buffer required for a given input sequence.

+ * + * @return The maximum number of bytes that will be produced per + * character of input + */ + public final float maxBytesPerChar() { + return maxBytesPerChar; + } + + /** + * Encodes as many characters as possible from the given input buffer, + * writing the results to the given output buffer. + * + *

The buffers are read from, and written to, starting at their current + * positions. At most {@link Buffer#remaining in.remaining()} characters + * will be read and at most {@link Buffer#remaining out.remaining()} + * bytes will be written. The buffers' positions will be advanced to + * reflect the characters read and the bytes written, but their marks and + * limits will not be modified. + * + *

In addition to reading characters from the input buffer and writing + * bytes to the output buffer, this method returns a {@link CoderResult} + * object to describe its reason for termination: + * + *

+ * + * In any case, if this method is to be reinvoked in the same encoding + * operation then care should be taken to preserve any characters remaining + * in the input buffer so that they are available to the next invocation. + * + *

The endOfInput parameter advises this method as to whether + * the invoker can provide further input beyond that contained in the given + * input buffer. If there is a possibility of providing additional input + * then the invoker should pass false for this parameter; if there + * is no possibility of providing further input then the invoker should + * pass true. It is not erroneous, and in fact it is quite + * common, to pass false in one invocation and later discover that + * no further input was actually available. It is critical, however, that + * the final invocation of this method in a sequence of invocations always + * pass true so that any remaining unencoded input will be treated + * as being malformed. + * + *

This method works by invoking the {@link #encodeLoop encodeLoop} + * method, interpreting its results, handling error conditions, and + * reinvoking it as necessary.

+ * + * + * @param in + * The input character buffer + * + * @param out + * The output byte buffer + * + * @param endOfInput + * true if, and only if, the invoker can provide no + * additional input characters beyond those in the given buffer + * + * @return A coder-result object describing the reason for termination + * + * @throws IllegalStateException + * If an encoding operation is already in progress and the previous + * step was an invocation neither of the {@link #reset reset} + * method, nor of this method with a value of false for + * the endOfInput parameter, nor of this method with a + * value of true for the endOfInput parameter + * but a return value indicating an incomplete encoding operation + * + * @throws CoderMalfunctionError + * If an invocation of the encodeLoop method threw + * an unexpected exception + */ + public final CoderResult encode(CharBuffer in, ByteBuffer out, + boolean endOfInput) + { + int newState = endOfInput ? ST_END : ST_CODING; + if ((state != ST_RESET) && (state != ST_CODING) + && !(endOfInput && (state == ST_END))) + throwIllegalStateException(state, newState); + state = newState; + + for (;;) { + + CoderResult cr; + try { + cr = encodeLoop(in, out); + } catch (BufferUnderflowException x) { + throw new CoderMalfunctionError(x); + } catch (BufferOverflowException x) { + throw new CoderMalfunctionError(x); + } + + if (cr.isOverflow()) + return cr; + + if (cr.isUnderflow()) { + if (endOfInput && in.hasRemaining()) { + cr = CoderResult.malformedForLength(in.remaining()); + // Fall through to malformed-input case + } else { + return cr; + } + } + + CodingErrorAction action = null; + if (cr.isMalformed()) + action = malformedInputAction; + else if (cr.isUnmappable()) + action = unmappableCharacterAction; + else + assert false : cr.toString(); + + if (action == CodingErrorAction.REPORT) + return cr; + + if (action == CodingErrorAction.REPLACE) { + if (out.remaining() < replacement.length) + return CoderResult.OVERFLOW; + out.put(replacement); + } + + if ((action == CodingErrorAction.IGNORE) + || (action == CodingErrorAction.REPLACE)) { + // Skip erroneous input either way + in.position(in.position() + cr.length()); + continue; + } + + assert false; + } + + } + + /** + * Flushes this encoder. + * + *

Some encoders maintain internal state and may need to write some + * final bytes to the output buffer once the overall input sequence has + * been read. + * + *

Any additional output is written to the output buffer beginning at + * its current position. At most {@link Buffer#remaining out.remaining()} + * bytes will be written. The buffer's position will be advanced + * appropriately, but its mark and limit will not be modified. + * + *

If this method completes successfully then it returns {@link + * CoderResult#UNDERFLOW}. If there is insufficient room in the output + * buffer then it returns {@link CoderResult#OVERFLOW}. If this happens + * then this method must be invoked again, with an output buffer that has + * more room, in order to complete the current encoding + * operation. + * + *

If this encoder has already been flushed then invoking this method + * has no effect. + * + *

This method invokes the {@link #implFlush implFlush} method to + * perform the actual flushing operation.

+ * + * @param out + * The output byte buffer + * + * @return A coder-result object, either {@link CoderResult#UNDERFLOW} or + * {@link CoderResult#OVERFLOW} + * + * @throws IllegalStateException + * If the previous step of the current encoding operation was an + * invocation neither of the {@link #flush flush} method nor of + * the three-argument {@link + * #encode(CharBuffer,ByteBuffer,boolean) encode} method + * with a value of true for the endOfInput + * parameter + */ + public final CoderResult flush(ByteBuffer out) { + if (state == ST_END) { + CoderResult cr = implFlush(out); + if (cr.isUnderflow()) + state = ST_FLUSHED; + return cr; + } + + if (state != ST_FLUSHED) + throwIllegalStateException(state, ST_FLUSHED); + + return CoderResult.UNDERFLOW; // Already flushed + } + + /** + * Flushes this encoder. + * + *

The default implementation of this method does nothing, and always + * returns {@link CoderResult#UNDERFLOW}. This method should be overridden + * by encoders that may need to write final bytes to the output buffer + * once the entire input sequence has been read.

+ * + * @param out + * The output byte buffer + * + * @return A coder-result object, either {@link CoderResult#UNDERFLOW} or + * {@link CoderResult#OVERFLOW} + */ + protected CoderResult implFlush(ByteBuffer out) { + return CoderResult.UNDERFLOW; + } + + /** + * Resets this encoder, clearing any internal state. + * + *

This method resets charset-independent state and also invokes the + * {@link #implReset() implReset} method in order to perform any + * charset-specific reset actions.

+ * + * @return This encoder + * + */ + public final CharsetEncoder reset() { + implReset(); + state = ST_RESET; + return this; + } + + /** + * Resets this encoder, clearing any charset-specific internal state. + * + *

The default implementation of this method does nothing. This method + * should be overridden by encoders that maintain internal state.

+ */ + protected void implReset() { } + + /** + * Encodes one or more characters into one or more bytes. + * + *

This method encapsulates the basic encoding loop, encoding as many + * characters as possible until it either runs out of input, runs out of room + * in the output buffer, or encounters an encoding error. This method is + * invoked by the {@link #encode encode} method, which handles result + * interpretation and error recovery. + * + *

The buffers are read from, and written to, starting at their current + * positions. At most {@link Buffer#remaining in.remaining()} characters + * will be read, and at most {@link Buffer#remaining out.remaining()} + * bytes will be written. The buffers' positions will be advanced to + * reflect the characters read and the bytes written, but their marks and + * limits will not be modified. + * + *

This method returns a {@link CoderResult} object to describe its + * reason for termination, in the same manner as the {@link #encode encode} + * method. Most implementations of this method will handle encoding errors + * by returning an appropriate result object for interpretation by the + * {@link #encode encode} method. An optimized implementation may instead + * examine the relevant error action and implement that action itself. + * + *

An implementation of this method may perform arbitrary lookahead by + * returning {@link CoderResult#UNDERFLOW} until it receives sufficient + * input.

+ * + * @param in + * The input character buffer + * + * @param out + * The output byte buffer + * + * @return A coder-result object describing the reason for termination + */ + protected abstract CoderResult encodeLoop(CharBuffer in, + ByteBuffer out); + + /** + * Convenience method that encodes the remaining content of a single input + * character buffer into a newly-allocated byte buffer. + * + *

This method implements an entire encoding + * operation; that is, it resets this encoder, then it encodes the + * characters in the given character buffer, and finally it flushes this + * encoder. This method should therefore not be invoked if an encoding + * operation is already in progress.

+ * + * @param in + * The input character buffer + * + * @return A newly-allocated byte buffer containing the result of the + * encoding operation. The buffer's position will be zero and its + * limit will follow the last byte written. + * + * @throws IllegalStateException + * If an encoding operation is already in progress + * + * @throws MalformedInputException + * If the character sequence starting at the input buffer's current + * position is not a legal sixteen-bit Unicode sequence and the current malformed-input action + * is {@link CodingErrorAction#REPORT} + * + * @throws UnmappableCharacterException + * If the character sequence starting at the input buffer's current + * position cannot be mapped to an equivalent byte sequence and + * the current unmappable-character action is {@link + * CodingErrorAction#REPORT} + */ + public final ByteBuffer encode(CharBuffer in) + throws CharacterCodingException + { + int n = (int)(in.remaining() * averageBytesPerChar()); + ByteBuffer out = ByteBuffer.allocate(n); + + if ((n == 0) && (in.remaining() == 0)) + return out; + reset(); + for (;;) { + CoderResult cr = in.hasRemaining() ? + encode(in, out, true) : CoderResult.UNDERFLOW; + if (cr.isUnderflow()) + cr = flush(out); + + if (cr.isUnderflow()) + break; + if (cr.isOverflow()) { + n = 2*n + 1; // Ensure progress; n might be 0! + ByteBuffer o = ByteBuffer.allocate(n); + out.flip(); + o.put(out); + out = o; + continue; + } + cr.throwException(); + } + out.flip(); + return out; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + private boolean canEncode(CharBuffer cb) { + if (state == ST_FLUSHED) + reset(); + else if (state != ST_RESET) + throwIllegalStateException(state, ST_CODING); + CodingErrorAction ma = malformedInputAction(); + CodingErrorAction ua = unmappableCharacterAction(); + try { + onMalformedInput(CodingErrorAction.REPORT); + onUnmappableCharacter(CodingErrorAction.REPORT); + encode(cb); + } catch (CharacterCodingException x) { + return false; + } finally { + onMalformedInput(ma); + onUnmappableCharacter(ua); + reset(); + } + return true; + } + + /** + * Tells whether or not this encoder can encode the given character. + * + *

This method returns false if the given character is a + * surrogate character; such characters can be interpreted only when they + * are members of a pair consisting of a high surrogate followed by a low + * surrogate. The {@link #canEncode(java.lang.CharSequence) + * canEncode(CharSequence)} method may be used to test whether or not a + * character sequence can be encoded. + * + *

This method may modify this encoder's state; it should therefore not + * be invoked if an encoding operation is already in + * progress. + * + *

The default implementation of this method is not very efficient; it + * should generally be overridden to improve performance.

+ * + * @return true if, and only if, this encoder can encode + * the given character + * + * @throws IllegalStateException + * If an encoding operation is already in progress + */ + public boolean canEncode(char c) { + CharBuffer cb = CharBuffer.allocate(1); + cb.put(c); + cb.flip(); + return canEncode(cb); + } + + /** + * Tells whether or not this encoder can encode the given character + * sequence. + * + *

If this method returns false for a particular character + * sequence then more information about why the sequence cannot be encoded + * may be obtained by performing a full encoding + * operation. + * + *

This method may modify this encoder's state; it should therefore not + * be invoked if an encoding operation is already in progress. + * + *

The default implementation of this method is not very efficient; it + * should generally be overridden to improve performance.

+ * + * @return true if, and only if, this encoder can encode + * the given character without throwing any exceptions and without + * performing any replacements + * + * @throws IllegalStateException + * If an encoding operation is already in progress + */ + public boolean canEncode(CharSequence cs) { + CharBuffer cb; + if (cs instanceof CharBuffer) + cb = ((CharBuffer)cs).duplicate(); + else + cb = CharBuffer.wrap(cs.toString()); + return canEncode(cb); + } + + + + + private void throwIllegalStateException(int from, int to) { + throw new IllegalStateException("Current state = " + stateNames[from] + + ", new state = " + stateNames[to]); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/nio/charset/IllegalCharsetNameException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/IllegalCharsetNameException.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. + * + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +// -- This file was mechanically generated: Do not edit! -- // + +package java.nio.charset; + + +/** + * Unchecked exception thrown when a string that is not a + * legal charset name is used as such. + * + * @since 1.4 + */ + +public class IllegalCharsetNameException + extends IllegalArgumentException +{ + + private static final long serialVersionUID = 1457525358470002989L; + + private String charsetName; + + /** + * Constructs an instance of this class.

+ * + * @param charsetName + * The illegal charset name + */ + public IllegalCharsetNameException(String charsetName) { + super(String.valueOf(charsetName)); + this.charsetName = charsetName; + } + + /** + * Retrieves the illegal charset name.

+ * + * @return The illegal charset name + */ + public String getCharsetName() { + return charsetName; + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/nio/charset/UnsupportedCharsetException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/nio/charset/UnsupportedCharsetException.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. + * + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +// -- This file was mechanically generated: Do not edit! -- // + +package java.nio.charset; + + +/** + * Unchecked exception thrown when no support is available + * for a requested charset. + * + * @since 1.4 + */ + +public class UnsupportedCharsetException + extends IllegalArgumentException +{ + + private static final long serialVersionUID = 1490765524727386367L; + + private String charsetName; + + /** + * Constructs an instance of this class.

+ * + * @param charsetName + * The name of the unsupported charset + */ + public UnsupportedCharsetException(String charsetName) { + super(String.valueOf(charsetName)); + this.charsetName = charsetName; + } + + /** + * Retrieves the name of the unsupported charset.

+ * + * @return The name of the unsupported charset + */ + public String getCharsetName() { + return charsetName; + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/security/AccessController.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/security/AccessController.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,557 @@ +/* + * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.security; + +import sun.security.util.Debug; + +/** + *

The AccessController class is used for access control operations + * and decisions. + * + *

More specifically, the AccessController class is used for + * three purposes: + * + *

    + *
  • to decide whether an access to a critical system + * resource is to be allowed or denied, based on the security policy + * currently in effect,

    + *

  • to mark code as being "privileged", thus affecting subsequent + * access determinations, and

    + *

  • to obtain a "snapshot" of the current calling context so + * access-control decisions from a different context can be made with + * respect to the saved context.
+ * + *

The {@link #checkPermission(Permission) checkPermission} method + * determines whether the access request indicated by a specified + * permission should be granted or denied. A sample call appears + * below. In this example, checkPermission will determine + * whether or not to grant "read" access to the file named "testFile" in + * the "/temp" directory. + * + *

+ *
+ * FilePermission perm = new FilePermission("/temp/testFile", "read");
+ * AccessController.checkPermission(perm);
+ *
+ * 
+ * + *

If a requested access is allowed, + * checkPermission returns quietly. If denied, an + * AccessControlException is + * thrown. AccessControlException can also be thrown if the requested + * permission is of an incorrect type or contains an invalid value. + * Such information is given whenever possible. + * + * Suppose the current thread traversed m callers, in the order of caller 1 + * to caller 2 to caller m. Then caller m invoked the + * checkPermission method. + * The checkPermission method determines whether access + * is granted or denied based on the following algorithm: + * + *

 {@code
+ * for (int i = m; i > 0; i--) {
+ *
+ *     if (caller i's domain does not have the permission)
+ *         throw AccessControlException
+ *
+ *     else if (caller i is marked as privileged) {
+ *         if (a context was specified in the call to doPrivileged)
+ *             context.checkPermission(permission)
+ *         return;
+ *     }
+ * };
+ *
+ * // Next, check the context inherited when the thread was created.
+ * // Whenever a new thread is created, the AccessControlContext at
+ * // that time is stored and associated with the new thread, as the
+ * // "inherited" context.
+ *
+ * inheritedContext.checkPermission(permission);
+ * }
+ * + *

A caller can be marked as being "privileged" + * (see {@link #doPrivileged(PrivilegedAction) doPrivileged} and below). + * When making access control decisions, the checkPermission + * method stops checking if it reaches a caller that + * was marked as "privileged" via a doPrivileged + * call without a context argument (see below for information about a + * context argument). If that caller's domain has the + * specified permission, no further checking is done and + * checkPermission + * returns quietly, indicating that the requested access is allowed. + * If that domain does not have the specified permission, an exception + * is thrown, as usual. + * + *

The normal use of the "privileged" feature is as follows. If you + * don't need to return a value from within the "privileged" block, do + * the following: + * + *

 {@code
+ * somemethod() {
+ *     ...normal code here...
+ *     AccessController.doPrivileged(new PrivilegedAction() {
+ *         public Void run() {
+ *             // privileged code goes here, for example:
+ *             System.loadLibrary("awt");
+ *             return null; // nothing to return
+ *         }
+ *     });
+ *     ...normal code here...
+ * }}
+ * + *

+ * PrivilegedAction is an interface with a single method, named + * run. + * The above example shows creation of an implementation + * of that interface; a concrete implementation of the + * run method is supplied. + * When the call to doPrivileged is made, an + * instance of the PrivilegedAction implementation is passed + * to it. The doPrivileged method calls the + * run method from the PrivilegedAction + * implementation after enabling privileges, and returns the + * run method's return value as the + * doPrivileged return value (which is + * ignored in this example). + * + *

If you need to return a value, you can do something like the following: + * + *

 {@code
+ * somemethod() {
+ *     ...normal code here...
+ *     String user = AccessController.doPrivileged(
+ *         new PrivilegedAction() {
+ *         public String run() {
+ *             return System.getProperty("user.name");
+ *             }
+ *         });
+ *     ...normal code here...
+ * }}
+ * + *

If the action performed in your run method could + * throw a "checked" exception (those listed in the throws clause + * of a method), then you need to use the + * PrivilegedExceptionAction interface instead of the + * PrivilegedAction interface: + * + *

 {@code
+ * somemethod() throws FileNotFoundException {
+ *     ...normal code here...
+ *     try {
+ *         FileInputStream fis = AccessController.doPrivileged(
+ *         new PrivilegedExceptionAction() {
+ *             public FileInputStream run() throws FileNotFoundException {
+ *                 return new FileInputStream("someFile");
+ *             }
+ *         });
+ *     } catch (PrivilegedActionException e) {
+ *         // e.getException() should be an instance of FileNotFoundException,
+ *         // as only "checked" exceptions will be "wrapped" in a
+ *         // PrivilegedActionException.
+ *         throw (FileNotFoundException) e.getException();
+ *     }
+ *     ...normal code here...
+ *  }}
+ * + *

Be *very* careful in your use of the "privileged" construct, and + * always remember to make the privileged code section as small as possible. + * + *

Note that checkPermission always performs security checks + * within the context of the currently executing thread. + * Sometimes a security check that should be made within a given context + * will actually need to be done from within a + * different context (for example, from within a worker thread). + * The {@link #getContext() getContext} method and + * AccessControlContext class are provided + * for this situation. The getContext method takes a "snapshot" + * of the current calling context, and places + * it in an AccessControlContext object, which it returns. A sample call is + * the following: + * + *

+ *
+ * AccessControlContext acc = AccessController.getContext()
+ *
+ * 
+ * + *

+ * AccessControlContext itself has a checkPermission method + * that makes access decisions based on the context it encapsulates, + * rather than that of the current execution thread. + * Code within a different context can thus call that method on the + * previously-saved AccessControlContext object. A sample call is the + * following: + * + *

+ *
+ * acc.checkPermission(permission)
+ *
+ * 
+ * + *

There are also times where you don't know a priori which permissions + * to check the context against. In these cases you can use the + * doPrivileged method that takes a context: + * + *

 {@code
+ * somemethod() {
+ *     AccessController.doPrivileged(new PrivilegedAction() {
+ *         public Object run() {
+ *             // Code goes here. Any permission checks within this
+ *             // run method will require that the intersection of the
+ *             // callers protection domain and the snapshot's
+ *             // context have the desired permission.
+ *         }
+ *     }, acc);
+ *     ...normal code here...
+ * }}
+ *
+ * @see AccessControlContext
+ *
+ * @author Li Gong
+ * @author Roland Schemers
+ */
+
+public final class AccessController {
+
+    /**
+     * Don't allow anyone to instantiate an AccessController
+     */
+    private AccessController() { }
+
+    /**
+     * Performs the specified PrivilegedAction with privileges
+     * enabled. The action is performed with all of the permissions
+     * possessed by the caller's protection domain.
+     *
+     * 

If the action's run method throws an (unchecked) + * exception, it will propagate through this method. + * + *

Note that any DomainCombiner associated with the current + * AccessControlContext will be ignored while the action is performed. + * + * @param action the action to be performed. + * + * @return the value returned by the action's run method. + * + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction,AccessControlContext) + * @see #doPrivileged(PrivilegedExceptionAction) + * @see #doPrivilegedWithCombiner(PrivilegedAction) + * @see java.security.DomainCombiner + */ + + public static native T doPrivileged(PrivilegedAction action); + + /** + * Performs the specified PrivilegedAction with privileges + * enabled. The action is performed with all of the permissions + * possessed by the caller's protection domain. + * + *

If the action's run method throws an (unchecked) + * exception, it will propagate through this method. + * + *

This method preserves the current AccessControlContext's + * DomainCombiner (which may be null) while the action is performed. + * + * @param action the action to be performed. + * + * @return the value returned by the action's run method. + * + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see java.security.DomainCombiner + * + * @since 1.6 + */ + public static T doPrivilegedWithCombiner(PrivilegedAction action) { + + DomainCombiner dc = null; + AccessControlContext acc = getStackAccessControlContext(); + if (acc == null || (dc = acc.getAssignedCombiner()) == null) { + return AccessController.doPrivileged(action); + } + return AccessController.doPrivileged(action, preserveCombiner(dc)); + } + + + /** + * Performs the specified PrivilegedAction with privileges + * enabled and restricted by the specified + * AccessControlContext. + * The action is performed with the intersection of the permissions + * possessed by the caller's protection domain, and those possessed + * by the domains represented by the specified + * AccessControlContext. + *

+ * If the action's run method throws an (unchecked) exception, + * it will propagate through this method. + * + * @param action the action to be performed. + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * + * @return the value returned by the action's run method. + * + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + */ + public static native T doPrivileged(PrivilegedAction action, + AccessControlContext context); + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled. The action is performed with all of the + * permissions possessed by the caller's protection domain. + * + *

If the action's run method throws an unchecked + * exception, it will propagate through this method. + * + *

Note that any DomainCombiner associated with the current + * AccessControlContext will be ignored while the action is performed. + * + * @param action the action to be performed + * + * @return the value returned by the action's run method + * + * @exception PrivilegedActionException if the specified action's + * run method threw a checked exception + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * @see #doPrivilegedWithCombiner(PrivilegedExceptionAction) + * @see java.security.DomainCombiner + */ + public static native T + doPrivileged(PrivilegedExceptionAction action) + throws PrivilegedActionException; + + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled. The action is performed with all of the + * permissions possessed by the caller's protection domain. + * + *

If the action's run method throws an unchecked + * exception, it will propagate through this method. + * + *

This method preserves the current AccessControlContext's + * DomainCombiner (which may be null) while the action is performed. + * + * @param action the action to be performed. + * + * @return the value returned by the action's run method + * + * @exception PrivilegedActionException if the specified action's + * run method threw a checked exception + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * @see java.security.DomainCombiner + * + * @since 1.6 + */ + public static T doPrivilegedWithCombiner + (PrivilegedExceptionAction action) throws PrivilegedActionException { + + DomainCombiner dc = null; + AccessControlContext acc = getStackAccessControlContext(); + if (acc == null || (dc = acc.getAssignedCombiner()) == null) { + return AccessController.doPrivileged(action); + } + return AccessController.doPrivileged(action, preserveCombiner(dc)); + } + + /** + * preserve the combiner across the doPrivileged call + */ + private static AccessControlContext preserveCombiner + (DomainCombiner combiner) { + + /** + * callerClass[0] = Reflection.getCallerClass + * callerClass[1] = AccessController.preserveCombiner + * callerClass[2] = AccessController.doPrivileged + * callerClass[3] = caller + */ + final Class callerClass = sun.reflect.Reflection.getCallerClass(3); + ProtectionDomain callerPd = doPrivileged + (new PrivilegedAction() { + public ProtectionDomain run() { + return callerClass.getProtectionDomain(); + } + }); + + // perform 'combine' on the caller of doPrivileged, + // even if the caller is from the bootclasspath + ProtectionDomain[] pds = new ProtectionDomain[] {callerPd}; + return new AccessControlContext(combiner.combine(pds, null), combiner); + } + + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled and restricted by the specified + * AccessControlContext. The action is performed with the + * intersection of the permissions possessed by the caller's + * protection domain, and those possessed by the domains represented by the + * specified AccessControlContext. + *

+ * If the action's run method throws an unchecked + * exception, it will propagate through this method. + * + * @param action the action to be performed + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * + * @return the value returned by the action's run method + * + * @exception PrivilegedActionException if the specified action's + * run method + * threw a checked exception + * @exception NullPointerException if the action is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + */ + public static native T + doPrivileged(PrivilegedExceptionAction action, + AccessControlContext context) + throws PrivilegedActionException; + + /** + * Returns the AccessControl context. i.e., it gets + * the protection domains of all the callers on the stack, + * starting at the first class with a non-null + * ProtectionDomain. + * + * @return the access control context based on the current stack or + * null if there was only privileged system code. + */ + + private static native AccessControlContext getStackAccessControlContext(); + + /** + * Returns the "inherited" AccessControl context. This is the context + * that existed when the thread was created. Package private so + * AccessControlContext can use it. + */ + + static native AccessControlContext getInheritedAccessControlContext(); + + /** + * This method takes a "snapshot" of the current calling context, which + * includes the current Thread's inherited AccessControlContext, + * and places it in an AccessControlContext object. This context may then + * be checked at a later point, possibly in another thread. + * + * @see AccessControlContext + * + * @return the AccessControlContext based on the current context. + */ + + public static AccessControlContext getContext() + { + AccessControlContext acc = getStackAccessControlContext(); + if (acc == null) { + // all we had was privileged system code. We don't want + // to return null though, so we construct a real ACC. + return new AccessControlContext(null, true); + } else { + return acc.optimize(); + } + } + + /** + * Determines whether the access request indicated by the + * specified permission should be allowed or denied, based on + * the current AccessControlContext and security policy. + * This method quietly returns if the access request + * is permitted, or throws an AccessControlException otherwise. The + * getPermission method of the AccessControlException returns the + * perm Permission object instance. + * + * @param perm the requested permission. + * + * @exception AccessControlException if the specified permission + * is not permitted, based on the current security policy. + * @exception NullPointerException if the specified permission + * is null and is checked based on the + * security policy currently in effect. + */ + + public static void checkPermission(Permission perm) + throws AccessControlException + { + //System.err.println("checkPermission "+perm); + //Thread.currentThread().dumpStack(); + + if (perm == null) { + throw new NullPointerException("permission can't be null"); + } + + AccessControlContext stack = getStackAccessControlContext(); + // if context is null, we had privileged system code on the stack. + if (stack == null) { + Debug debug = AccessControlContext.getDebug(); + boolean dumpDebug = false; + if (debug != null) { + dumpDebug = !Debug.isOn("codebase="); + dumpDebug &= !Debug.isOn("permission=") || + Debug.isOn("permission=" + perm.getClass().getCanonicalName()); + } + + if (dumpDebug && Debug.isOn("stack")) { + Thread.currentThread().dumpStack(); + } + + if (dumpDebug && Debug.isOn("domain")) { + debug.println("domain (context is null)"); + } + + if (dumpDebug) { + debug.println("access allowed "+perm); + } + return; + } + + AccessControlContext acc = stack.optimize(); + acc.checkPermission(perm); + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/security/PrivilegedAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/security/PrivilegedAction.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,56 @@ +/* + * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.security; + + +/** + * A computation to be performed with privileges enabled. The computation is + * performed by invoking AccessController.doPrivileged on the + * PrivilegedAction object. This interface is used only for + * computations that do not throw checked exceptions; computations that + * throw checked exceptions must use PrivilegedExceptionAction + * instead. + * + * @see AccessController + * @see AccessController#doPrivileged(PrivilegedAction) + * @see PrivilegedExceptionAction + */ + +public interface PrivilegedAction { + /** + * Performs the computation. This method will be called by + * AccessController.doPrivileged after enabling privileges. + * + * @return a class-dependent value that may represent the results of the + * computation. Each class that implements + * PrivilegedAction + * should document what (if anything) this value represents. + * @see AccessController#doPrivileged(PrivilegedAction) + * @see AccessController#doPrivileged(PrivilegedAction, + * AccessControlContext) + */ + T run(); +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/security/PrivilegedActionException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/security/PrivilegedActionException.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,105 @@ +/* + * Copyright (c) 1998, 2001, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.security; + +/** + * This exception is thrown by + * doPrivileged(PrivilegedExceptionAction) and + * doPrivileged(PrivilegedExceptionAction, + * AccessControlContext context) to indicate + * that the action being performed threw a checked exception. The exception + * thrown by the action can be obtained by calling the + * getException method. In effect, an + * PrivilegedActionException is a "wrapper" + * for an exception thrown by a privileged action. + * + *

As of release 1.4, this exception has been retrofitted to conform to + * the general purpose exception-chaining mechanism. The "exception thrown + * by the privileged computation" that is provided at construction time and + * accessed via the {@link #getException()} method is now known as the + * cause, and may be accessed via the {@link Throwable#getCause()} + * method, as well as the aforementioned "legacy method." + * + * @see PrivilegedExceptionAction + * @see AccessController#doPrivileged(PrivilegedExceptionAction) + * @see AccessController#doPrivileged(PrivilegedExceptionAction,AccessControlContext) + */ +public class PrivilegedActionException extends Exception { + // use serialVersionUID from JDK 1.2.2 for interoperability + private static final long serialVersionUID = 4724086851538908602L; + + /** + * @serial + */ + private Exception exception; + + /** + * Constructs a new PrivilegedActionException "wrapping" + * the specific Exception. + * + * @param exception The exception thrown + */ + public PrivilegedActionException(Exception exception) { + super((Throwable)null); // Disallow initCause + this.exception = exception; + } + + /** + * Returns the exception thrown by the privileged computation that + * resulted in this PrivilegedActionException. + * + *

This method predates the general-purpose exception chaining facility. + * The {@link Throwable#getCause()} method is now the preferred means of + * obtaining this information. + * + * @return the exception thrown by the privileged computation that + * resulted in this PrivilegedActionException. + * @see PrivilegedExceptionAction + * @see AccessController#doPrivileged(PrivilegedExceptionAction) + * @see AccessController#doPrivileged(PrivilegedExceptionAction, + * AccessControlContext) + */ + public Exception getException() { + return exception; + } + + /** + * Returns the cause of this exception (the exception thrown by + * the privileged computation that resulted in this + * PrivilegedActionException). + * + * @return the cause of this exception. + * @since 1.4 + */ + public Throwable getCause() { + return exception; + } + + public String toString() { + String s = getClass().getName(); + return (exception != null) ? (s + ": " + exception.toString()) : s; + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/security/PrivilegedExceptionAction.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/security/PrivilegedExceptionAction.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,62 @@ +/* + * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.security; + + +/** + * A computation to be performed with privileges enabled, that throws one or + * more checked exceptions. The computation is performed by invoking + * AccessController.doPrivileged on the + * PrivilegedExceptionAction object. This interface is + * used only for computations that throw checked exceptions; + * computations that do not throw + * checked exceptions should use PrivilegedAction instead. + * + * @see AccessController + * @see AccessController#doPrivileged(PrivilegedExceptionAction) + * @see AccessController#doPrivileged(PrivilegedExceptionAction, + * AccessControlContext) + * @see PrivilegedAction + */ + +public interface PrivilegedExceptionAction { + /** + * Performs the computation. This method will be called by + * AccessController.doPrivileged after enabling privileges. + * + * @return a class-dependent value that may represent the results of the + * computation. Each class that implements + * PrivilegedExceptionAction should document what + * (if anything) this value represents. + * @throws Exception an exceptional condition has occurred. Each class + * that implements PrivilegedExceptionAction should + * document the exceptions that its run method can throw. + * @see AccessController#doPrivileged(PrivilegedExceptionAction) + * @see AccessController#doPrivileged(PrivilegedExceptionAction,AccessControlContext) + */ + + T run() throws Exception; +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/Annotation.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/Annotation.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,84 @@ +/* + * Copyright (c) 1997, 2002, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.text; + +/** +* An Annotation object is used as a wrapper for a text attribute value if +* the attribute has annotation characteristics. These characteristics are: +*

    +*
  • The text range that the attribute is applied to is critical to the +* semantics of the range. That means, the attribute cannot be applied to subranges +* of the text range that it applies to, and, if two adjacent text ranges have +* the same value for this attribute, the attribute still cannot be applied to +* the combined range as a whole with this value. +*
  • The attribute or its value usually do no longer apply if the underlying text is +* changed. +*
+* +* An example is grammatical information attached to a sentence: +* For the previous sentence, you can say that "an example" +* is the subject, but you cannot say the same about "an", "example", or "exam". +* When the text is changed, the grammatical information typically becomes invalid. +* Another example is Japanese reading information (yomi). +* +*

+* Wrapping the attribute value into an Annotation object guarantees that +* adjacent text runs don't get merged even if the attribute values are equal, +* and indicates to text containers that the attribute should be discarded if +* the underlying text is modified. +* +* @see AttributedCharacterIterator +* @since 1.2 +*/ + +public class Annotation { + + /** + * Constructs an annotation record with the given value, which + * may be null. + * @param value The value of the attribute + */ + public Annotation(Object value) { + this.value = value; + } + + /** + * Returns the value of the attribute, which may be null. + */ + public Object getValue() { + return value; + } + + /** + * Returns the String representation of this Annotation. + */ + public String toString() { + return getClass().getName() + "[value=" + value + "]"; + } + + private Object value; + +}; diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/AttributedCharacterIterator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/AttributedCharacterIterator.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,254 @@ +/* + * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * An {@code AttributedCharacterIterator} allows iteration through both text and + * related attribute information. + * + *

+ * An attribute is a key/value pair, identified by the key. No two + * attributes on a given character can have the same key. + * + *

The values for an attribute are immutable, or must not be mutated + * by clients or storage. They are always passed by reference, and not + * cloned. + * + *

A run with respect to an attribute is a maximum text range for + * which: + *

    + *
  • the attribute is undefined or {@code null} for the entire range, or + *
  • the attribute value is defined and has the same non-{@code null} value for the + * entire range. + *
+ * + *

A run with respect to a set of attributes is a maximum text range for + * which this condition is met for each member attribute. + * + *

When getting a run with no explicit attributes specified (i.e., + * calling {@link #getRunStart()} and {@link #getRunLimit()}), any + * contiguous text segments having the same attributes (the same set + * of attribute/value pairs) are treated as separate runs if the + * attributes have been given to those text segments separately. + * + *

The returned indexes are limited to the range of the iterator. + * + *

The returned attribute information is limited to runs that contain + * the current character. + * + *

+ * Attribute keys are instances of {@link AttributedCharacterIterator.Attribute} and its + * subclasses, such as {@link java.awt.font.TextAttribute}. + * + * @see AttributedCharacterIterator.Attribute + * @see java.awt.font.TextAttribute + * @see AttributedString + * @see Annotation + * @since 1.2 + */ + +public interface AttributedCharacterIterator extends CharacterIterator { + + /** + * Defines attribute keys that are used to identify text attributes. These + * keys are used in {@code AttributedCharacterIterator} and {@code AttributedString}. + * @see AttributedCharacterIterator + * @see AttributedString + * @since 1.2 + */ + + public static class Attribute implements Serializable { + + /** + * The name of this {@code Attribute}. The name is used primarily by {@code readResolve} + * to look up the corresponding predefined instance when deserializing + * an instance. + * @serial + */ + private String name; + + // table of all instances in this class, used by readResolve + private static final Map instanceMap = new HashMap(7); + + /** + * Constructs an {@code Attribute} with the given name. + */ + protected Attribute(String name) { + this.name = name; + if (this.getClass() == Attribute.class) { + instanceMap.put(name, this); + } + } + + /** + * Compares two objects for equality. This version only returns true + * for x.equals(y) if x and y refer + * to the same object, and guarantees this for all subclasses. + */ + public final boolean equals(Object obj) { + return super.equals(obj); + } + + /** + * Returns a hash code value for the object. This version is identical to + * the one in {@code Object}, but is also final. + */ + public final int hashCode() { + return super.hashCode(); + } + + /** + * Returns a string representation of the object. This version returns the + * concatenation of class name, {@code "("}, a name identifying the attribute + * and {@code ")"}. + */ + public String toString() { + return getClass().getName() + "(" + name + ")"; + } + + /** + * Returns the name of the attribute. + */ + protected String getName() { + return name; + } + + /** + * Resolves instances being deserialized to the predefined constants. + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != Attribute.class) { + throw new InvalidObjectException("subclass didn't correctly implement readResolve"); + } + + Attribute instance = (Attribute) instanceMap.get(getName()); + if (instance != null) { + return instance; + } else { + throw new InvalidObjectException("unknown attribute name"); + } + } + + /** + * Attribute key for the language of some text. + *

Values are instances of {@link java.util.Locale Locale}. + * @see java.util.Locale + */ + public static final Attribute LANGUAGE = new Attribute("language"); + + /** + * Attribute key for the reading of some text. In languages where the written form + * and the pronunciation of a word are only loosely related (such as Japanese), + * it is often necessary to store the reading (pronunciation) along with the + * written form. + *

Values are instances of {@link Annotation} holding instances of {@link String}. + * @see Annotation + * @see java.lang.String + */ + public static final Attribute READING = new Attribute("reading"); + + /** + * Attribute key for input method segments. Input methods often break + * up text into segments, which usually correspond to words. + *

Values are instances of {@link Annotation} holding a {@code null} reference. + * @see Annotation + */ + public static final Attribute INPUT_METHOD_SEGMENT = new Attribute("input_method_segment"); + + // make sure the serial version doesn't change between compiler versions + private static final long serialVersionUID = -9142742483513960612L; + + }; + + /** + * Returns the index of the first character of the run + * with respect to all attributes containing the current character. + * + *

Any contiguous text segments having the same attributes (the + * same set of attribute/value pairs) are treated as separate runs + * if the attributes have been given to those text segments separately. + */ + public int getRunStart(); + + /** + * Returns the index of the first character of the run + * with respect to the given {@code attribute} containing the current character. + */ + public int getRunStart(Attribute attribute); + + /** + * Returns the index of the first character of the run + * with respect to the given {@code attributes} containing the current character. + */ + public int getRunStart(Set attributes); + + /** + * Returns the index of the first character following the run + * with respect to all attributes containing the current character. + * + *

Any contiguous text segments having the same attributes (the + * same set of attribute/value pairs) are treated as separate runs + * if the attributes have been given to those text segments separately. + */ + public int getRunLimit(); + + /** + * Returns the index of the first character following the run + * with respect to the given {@code attribute} containing the current character. + */ + public int getRunLimit(Attribute attribute); + + /** + * Returns the index of the first character following the run + * with respect to the given {@code attributes} containing the current character. + */ + public int getRunLimit(Set attributes); + + /** + * Returns a map with the attributes defined on the current + * character. + */ + public Map getAttributes(); + + /** + * Returns the value of the named {@code attribute} for the current character. + * Returns {@code null} if the {@code attribute} is not defined. + */ + public Object getAttribute(Attribute attribute); + + /** + * Returns the keys of all attributes defined on the + * iterator's text range. The set is empty if no + * attributes are defined. + */ + public Set getAllAttributeKeys(); +}; diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/AttributedString.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/AttributedString.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,1120 @@ +/* + * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.text; + +import java.util.*; +import java.text.AttributedCharacterIterator.Attribute; + +/** + * An AttributedString holds text and related attribute information. It + * may be used as the actual data storage in some cases where a text + * reader wants to access attributed text through the AttributedCharacterIterator + * interface. + * + *

+ * An attribute is a key/value pair, identified by the key. No two + * attributes on a given character can have the same key. + * + *

The values for an attribute are immutable, or must not be mutated + * by clients or storage. They are always passed by reference, and not + * cloned. + * + * @see AttributedCharacterIterator + * @see Annotation + * @since 1.2 + */ + +public class AttributedString { + + // since there are no vectors of int, we have to use arrays. + // We allocate them in chunks of 10 elements so we don't have to allocate all the time. + private static final int ARRAY_SIZE_INCREMENT = 10; + + // field holding the text + String text; + + // fields holding run attribute information + // run attributes are organized by run + int runArraySize; // current size of the arrays + int runCount; // actual number of runs, <= runArraySize + int runStarts[]; // start index for each run + Vector runAttributes[]; // vector of attribute keys for each run + Vector runAttributeValues[]; // parallel vector of attribute values for each run + + /** + * Constructs an AttributedString instance with the given + * AttributedCharacterIterators. + * + * @param iterators AttributedCharacterIterators to construct + * AttributedString from. + * @throws NullPointerException if iterators is null + */ + AttributedString(AttributedCharacterIterator[] iterators) { + if (iterators == null) { + throw new NullPointerException("Iterators must not be null"); + } + if (iterators.length == 0) { + text = ""; + } + else { + // Build the String contents + StringBuffer buffer = new StringBuffer(); + for (int counter = 0; counter < iterators.length; counter++) { + appendContents(buffer, iterators[counter]); + } + + text = buffer.toString(); + + if (text.length() > 0) { + // Determine the runs, creating a new run when the attributes + // differ. + int offset = 0; + Map last = null; + + for (int counter = 0; counter < iterators.length; counter++) { + AttributedCharacterIterator iterator = iterators[counter]; + int start = iterator.getBeginIndex(); + int end = iterator.getEndIndex(); + int index = start; + + while (index < end) { + iterator.setIndex(index); + + Map attrs = iterator.getAttributes(); + + if (mapsDiffer(last, attrs)) { + setAttributes(attrs, index - start + offset); + } + last = attrs; + index = iterator.getRunLimit(); + } + offset += (end - start); + } + } + } + } + + /** + * Constructs an AttributedString instance with the given text. + * @param text The text for this attributed string. + * @exception NullPointerException if text is null. + */ + public AttributedString(String text) { + if (text == null) { + throw new NullPointerException(); + } + this.text = text; + } + + /** + * Constructs an AttributedString instance with the given text and attributes. + * @param text The text for this attributed string. + * @param attributes The attributes that apply to the entire string. + * @exception NullPointerException if text or + * attributes is null. + * @exception IllegalArgumentException if the text has length 0 + * and the attributes parameter is not an empty Map (attributes + * cannot be applied to a 0-length range). + */ + public AttributedString(String text, + Map attributes) + { + if (text == null || attributes == null) { + throw new NullPointerException(); + } + this.text = text; + + if (text.length() == 0) { + if (attributes.isEmpty()) + return; + throw new IllegalArgumentException("Can't add attribute to 0-length text"); + } + + int attributeCount = attributes.size(); + if (attributeCount > 0) { + createRunAttributeDataVectors(); + Vector newRunAttributes = new Vector(attributeCount); + Vector newRunAttributeValues = new Vector(attributeCount); + runAttributes[0] = newRunAttributes; + runAttributeValues[0] = newRunAttributeValues; + Iterator iterator = attributes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + newRunAttributes.addElement(entry.getKey()); + newRunAttributeValues.addElement(entry.getValue()); + } + } + } + + /** + * Constructs an AttributedString instance with the given attributed + * text represented by AttributedCharacterIterator. + * @param text The text for this attributed string. + * @exception NullPointerException if text is null. + */ + public AttributedString(AttributedCharacterIterator text) { + // If performance is critical, this constructor should be + // implemented here rather than invoking the constructor for a + // subrange. We can avoid some range checking in the loops. + this(text, text.getBeginIndex(), text.getEndIndex(), null); + } + + /** + * Constructs an AttributedString instance with the subrange of + * the given attributed text represented by + * AttributedCharacterIterator. If the given range produces an + * empty text, all attributes will be discarded. Note that any + * attributes wrapped by an Annotation object are discarded for a + * subrange of the original attribute range. + * + * @param text The text for this attributed string. + * @param beginIndex Index of the first character of the range. + * @param endIndex Index of the character following the last character + * of the range. + * @exception NullPointerException if text is null. + * @exception IllegalArgumentException if the subrange given by + * beginIndex and endIndex is out of the text range. + * @see java.text.Annotation + */ + public AttributedString(AttributedCharacterIterator text, + int beginIndex, + int endIndex) { + this(text, beginIndex, endIndex, null); + } + + /** + * Constructs an AttributedString instance with the subrange of + * the given attributed text represented by + * AttributedCharacterIterator. Only attributes that match the + * given attributes will be incorporated into the instance. If the + * given range produces an empty text, all attributes will be + * discarded. Note that any attributes wrapped by an Annotation + * object are discarded for a subrange of the original attribute + * range. + * + * @param text The text for this attributed string. + * @param beginIndex Index of the first character of the range. + * @param endIndex Index of the character following the last character + * of the range. + * @param attributes Specifies attributes to be extracted + * from the text. If null is specified, all available attributes will + * be used. + * @exception NullPointerException if text is null. + * @exception IllegalArgumentException if the subrange given by + * beginIndex and endIndex is out of the text range. + * @see java.text.Annotation + */ + public AttributedString(AttributedCharacterIterator text, + int beginIndex, + int endIndex, + Attribute[] attributes) { + if (text == null) { + throw new NullPointerException(); + } + + // Validate the given subrange + int textBeginIndex = text.getBeginIndex(); + int textEndIndex = text.getEndIndex(); + if (beginIndex < textBeginIndex || endIndex > textEndIndex || beginIndex > endIndex) + throw new IllegalArgumentException("Invalid substring range"); + + // Copy the given string + StringBuffer textBuffer = new StringBuffer(); + text.setIndex(beginIndex); + for (char c = text.current(); text.getIndex() < endIndex; c = text.next()) + textBuffer.append(c); + this.text = textBuffer.toString(); + + if (beginIndex == endIndex) + return; + + // Select attribute keys to be taken care of + HashSet keys = new HashSet(); + if (attributes == null) { + keys.addAll(text.getAllAttributeKeys()); + } else { + for (int i = 0; i < attributes.length; i++) + keys.add(attributes[i]); + keys.retainAll(text.getAllAttributeKeys()); + } + if (keys.isEmpty()) + return; + + // Get and set attribute runs for each attribute name. Need to + // scan from the top of the text so that we can discard any + // Annotation that is no longer applied to a subset text segment. + Iterator itr = keys.iterator(); + while (itr.hasNext()) { + Attribute attributeKey = (Attribute)itr.next(); + text.setIndex(textBeginIndex); + while (text.getIndex() < endIndex) { + int start = text.getRunStart(attributeKey); + int limit = text.getRunLimit(attributeKey); + Object value = text.getAttribute(attributeKey); + + if (value != null) { + if (value instanceof Annotation) { + if (start >= beginIndex && limit <= endIndex) { + addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex); + } else { + if (limit > endIndex) + break; + } + } else { + // if the run is beyond the given (subset) range, we + // don't need to process further. + if (start >= endIndex) + break; + if (limit > beginIndex) { + // attribute is applied to any subrange + if (start < beginIndex) + start = beginIndex; + if (limit > endIndex) + limit = endIndex; + if (start != limit) { + addAttribute(attributeKey, value, start - beginIndex, limit - beginIndex); + } + } + } + } + text.setIndex(limit); + } + } + } + + /** + * Adds an attribute to the entire string. + * @param attribute the attribute key + * @param value the value of the attribute; may be null + * @exception NullPointerException if attribute is null. + * @exception IllegalArgumentException if the AttributedString has length 0 + * (attributes cannot be applied to a 0-length range). + */ + public void addAttribute(Attribute attribute, Object value) { + + if (attribute == null) { + throw new NullPointerException(); + } + + int len = length(); + if (len == 0) { + throw new IllegalArgumentException("Can't add attribute to 0-length text"); + } + + addAttributeImpl(attribute, value, 0, len); + } + + /** + * Adds an attribute to a subrange of the string. + * @param attribute the attribute key + * @param value The value of the attribute. May be null. + * @param beginIndex Index of the first character of the range. + * @param endIndex Index of the character following the last character of the range. + * @exception NullPointerException if attribute is null. + * @exception IllegalArgumentException if beginIndex is less then 0, endIndex is + * greater than the length of the string, or beginIndex and endIndex together don't + * define a non-empty subrange of the string. + */ + public void addAttribute(Attribute attribute, Object value, + int beginIndex, int endIndex) { + + if (attribute == null) { + throw new NullPointerException(); + } + + if (beginIndex < 0 || endIndex > length() || beginIndex >= endIndex) { + throw new IllegalArgumentException("Invalid substring range"); + } + + addAttributeImpl(attribute, value, beginIndex, endIndex); + } + + /** + * Adds a set of attributes to a subrange of the string. + * @param attributes The attributes to be added to the string. + * @param beginIndex Index of the first character of the range. + * @param endIndex Index of the character following the last + * character of the range. + * @exception NullPointerException if attributes is null. + * @exception IllegalArgumentException if beginIndex is less then + * 0, endIndex is greater than the length of the string, or + * beginIndex and endIndex together don't define a non-empty + * subrange of the string and the attributes parameter is not an + * empty Map. + */ + public void addAttributes(Map attributes, + int beginIndex, int endIndex) + { + if (attributes == null) { + throw new NullPointerException(); + } + + if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) { + throw new IllegalArgumentException("Invalid substring range"); + } + if (beginIndex == endIndex) { + if (attributes.isEmpty()) + return; + throw new IllegalArgumentException("Can't add attribute to 0-length text"); + } + + // make sure we have run attribute data vectors + if (runCount == 0) { + createRunAttributeDataVectors(); + } + + // break up runs if necessary + int beginRunIndex = ensureRunBreak(beginIndex); + int endRunIndex = ensureRunBreak(endIndex); + + Iterator iterator = attributes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + addAttributeRunData((Attribute) entry.getKey(), entry.getValue(), beginRunIndex, endRunIndex); + } + } + + private synchronized void addAttributeImpl(Attribute attribute, Object value, + int beginIndex, int endIndex) { + + // make sure we have run attribute data vectors + if (runCount == 0) { + createRunAttributeDataVectors(); + } + + // break up runs if necessary + int beginRunIndex = ensureRunBreak(beginIndex); + int endRunIndex = ensureRunBreak(endIndex); + + addAttributeRunData(attribute, value, beginRunIndex, endRunIndex); + } + + private final void createRunAttributeDataVectors() { + // use temporary variables so things remain consistent in case of an exception + int newRunStarts[] = new int[ARRAY_SIZE_INCREMENT]; + Vector newRunAttributes[] = new Vector[ARRAY_SIZE_INCREMENT]; + Vector newRunAttributeValues[] = new Vector[ARRAY_SIZE_INCREMENT]; + runStarts = newRunStarts; + runAttributes = newRunAttributes; + runAttributeValues = newRunAttributeValues; + runArraySize = ARRAY_SIZE_INCREMENT; + runCount = 1; // assume initial run starting at index 0 + } + + // ensure there's a run break at offset, return the index of the run + private final int ensureRunBreak(int offset) { + return ensureRunBreak(offset, true); + } + + /** + * Ensures there is a run break at offset, returning the index of + * the run. If this results in splitting a run, two things can happen: + *

    + *
  • If copyAttrs is true, the attributes from the existing run + * will be placed in both of the newly created runs. + *
  • If copyAttrs is false, the attributes from the existing run + * will NOT be copied to the run to the right (>= offset) of the break, + * but will exist on the run to the left (< offset). + *
+ */ + private final int ensureRunBreak(int offset, boolean copyAttrs) { + if (offset == length()) { + return runCount; + } + + // search for the run index where this offset should be + int runIndex = 0; + while (runIndex < runCount && runStarts[runIndex] < offset) { + runIndex++; + } + + // if the offset is at a run start already, we're done + if (runIndex < runCount && runStarts[runIndex] == offset) { + return runIndex; + } + + // we'll have to break up a run + // first, make sure we have enough space in our arrays + if (runCount == runArraySize) { + int newArraySize = runArraySize + ARRAY_SIZE_INCREMENT; + int newRunStarts[] = new int[newArraySize]; + Vector newRunAttributes[] = new Vector[newArraySize]; + Vector newRunAttributeValues[] = new Vector[newArraySize]; + for (int i = 0; i < runArraySize; i++) { + newRunStarts[i] = runStarts[i]; + newRunAttributes[i] = runAttributes[i]; + newRunAttributeValues[i] = runAttributeValues[i]; + } + runStarts = newRunStarts; + runAttributes = newRunAttributes; + runAttributeValues = newRunAttributeValues; + runArraySize = newArraySize; + } + + // make copies of the attribute information of the old run that the new one used to be part of + // use temporary variables so things remain consistent in case of an exception + Vector newRunAttributes = null; + Vector newRunAttributeValues = null; + + if (copyAttrs) { + Vector oldRunAttributes = runAttributes[runIndex - 1]; + Vector oldRunAttributeValues = runAttributeValues[runIndex - 1]; + if (oldRunAttributes != null) { + newRunAttributes = (Vector) oldRunAttributes.clone(); + } + if (oldRunAttributeValues != null) { + newRunAttributeValues = (Vector) oldRunAttributeValues.clone(); + } + } + + // now actually break up the run + runCount++; + for (int i = runCount - 1; i > runIndex; i--) { + runStarts[i] = runStarts[i - 1]; + runAttributes[i] = runAttributes[i - 1]; + runAttributeValues[i] = runAttributeValues[i - 1]; + } + runStarts[runIndex] = offset; + runAttributes[runIndex] = newRunAttributes; + runAttributeValues[runIndex] = newRunAttributeValues; + + return runIndex; + } + + // add the attribute attribute/value to all runs where beginRunIndex <= runIndex < endRunIndex + private void addAttributeRunData(Attribute attribute, Object value, + int beginRunIndex, int endRunIndex) { + + for (int i = beginRunIndex; i < endRunIndex; i++) { + int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet + if (runAttributes[i] == null) { + Vector newRunAttributes = new Vector(); + Vector newRunAttributeValues = new Vector(); + runAttributes[i] = newRunAttributes; + runAttributeValues[i] = newRunAttributeValues; + } else { + // check whether we have an entry already + keyValueIndex = runAttributes[i].indexOf(attribute); + } + + if (keyValueIndex == -1) { + // create new entry + int oldSize = runAttributes[i].size(); + runAttributes[i].addElement(attribute); + try { + runAttributeValues[i].addElement(value); + } + catch (Exception e) { + runAttributes[i].setSize(oldSize); + runAttributeValues[i].setSize(oldSize); + } + } else { + // update existing entry + runAttributeValues[i].set(keyValueIndex, value); + } + } + } + + /** + * Creates an AttributedCharacterIterator instance that provides access to the entire contents of + * this string. + * + * @return An iterator providing access to the text and its attributes. + */ + public AttributedCharacterIterator getIterator() { + return getIterator(null, 0, length()); + } + + /** + * Creates an AttributedCharacterIterator instance that provides access to + * selected contents of this string. + * Information about attributes not listed in attributes that the + * implementor may have need not be made accessible through the iterator. + * If the list is null, all available attribute information should be made + * accessible. + * + * @param attributes a list of attributes that the client is interested in + * @return an iterator providing access to the entire text and its selected attributes + */ + public AttributedCharacterIterator getIterator(Attribute[] attributes) { + return getIterator(attributes, 0, length()); + } + + /** + * Creates an AttributedCharacterIterator instance that provides access to + * selected contents of this string. + * Information about attributes not listed in attributes that the + * implementor may have need not be made accessible through the iterator. + * If the list is null, all available attribute information should be made + * accessible. + * + * @param attributes a list of attributes that the client is interested in + * @param beginIndex the index of the first character + * @param endIndex the index of the character following the last character + * @return an iterator providing access to the text and its attributes + * @exception IllegalArgumentException if beginIndex is less then 0, + * endIndex is greater than the length of the string, or beginIndex is + * greater than endIndex. + */ + public AttributedCharacterIterator getIterator(Attribute[] attributes, int beginIndex, int endIndex) { + return new AttributedStringIterator(attributes, beginIndex, endIndex); + } + + // all (with the exception of length) reading operations are private, + // since AttributedString instances are accessed through iterators. + + // length is package private so that CharacterIteratorFieldDelegate can + // access it without creating an AttributedCharacterIterator. + int length() { + return text.length(); + } + + private char charAt(int index) { + return text.charAt(index); + } + + private synchronized Object getAttribute(Attribute attribute, int runIndex) { + Vector currentRunAttributes = runAttributes[runIndex]; + Vector currentRunAttributeValues = runAttributeValues[runIndex]; + if (currentRunAttributes == null) { + return null; + } + int attributeIndex = currentRunAttributes.indexOf(attribute); + if (attributeIndex != -1) { + return currentRunAttributeValues.elementAt(attributeIndex); + } + else { + return null; + } + } + + // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex + private Object getAttributeCheckRange(Attribute attribute, int runIndex, int beginIndex, int endIndex) { + Object value = getAttribute(attribute, runIndex); + if (value instanceof Annotation) { + // need to check whether the annotation's range extends outside the iterator's range + if (beginIndex > 0) { + int currIndex = runIndex; + int runStart = runStarts[currIndex]; + while (runStart >= beginIndex && + valuesMatch(value, getAttribute(attribute, currIndex - 1))) { + currIndex--; + runStart = runStarts[currIndex]; + } + if (runStart < beginIndex) { + // annotation's range starts before iterator's range + return null; + } + } + int textLength = length(); + if (endIndex < textLength) { + int currIndex = runIndex; + int runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength; + while (runLimit <= endIndex && + valuesMatch(value, getAttribute(attribute, currIndex + 1))) { + currIndex++; + runLimit = (currIndex < runCount - 1) ? runStarts[currIndex + 1] : textLength; + } + if (runLimit > endIndex) { + // annotation's range ends after iterator's range + return null; + } + } + // annotation's range is subrange of iterator's range, + // so we can return the value + } + return value; + } + + // returns whether all specified attributes have equal values in the runs with the given indices + private boolean attributeValuesMatch(Set attributes, int runIndex1, int runIndex2) { + Iterator iterator = attributes.iterator(); + while (iterator.hasNext()) { + Attribute key = (Attribute) iterator.next(); + if (!valuesMatch(getAttribute(key, runIndex1), getAttribute(key, runIndex2))) { + return false; + } + } + return true; + } + + // returns whether the two objects are either both null or equal + private final static boolean valuesMatch(Object value1, Object value2) { + if (value1 == null) { + return value2 == null; + } else { + return value1.equals(value2); + } + } + + /** + * Appends the contents of the CharacterIterator iterator into the + * StringBuffer buf. + */ + private final void appendContents(StringBuffer buf, + CharacterIterator iterator) { + int index = iterator.getBeginIndex(); + int end = iterator.getEndIndex(); + + while (index < end) { + iterator.setIndex(index++); + buf.append(iterator.current()); + } + } + + /** + * Sets the attributes for the range from offset to the next run break + * (typically the end of the text) to the ones specified in attrs. + * This is only meant to be called from the constructor! + */ + private void setAttributes(Map attrs, int offset) { + if (runCount == 0) { + createRunAttributeDataVectors(); + } + + int index = ensureRunBreak(offset, false); + int size; + + if (attrs != null && (size = attrs.size()) > 0) { + Vector runAttrs = new Vector(size); + Vector runValues = new Vector(size); + Iterator iterator = attrs.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry)iterator.next(); + + runAttrs.add(entry.getKey()); + runValues.add(entry.getValue()); + } + runAttributes[index] = runAttrs; + runAttributeValues[index] = runValues; + } + } + + /** + * Returns true if the attributes specified in last and attrs differ. + */ + private static boolean mapsDiffer(Map last, Map attrs) { + if (last == null) { + return (attrs != null && attrs.size() > 0); + } + return (!last.equals(attrs)); + } + + + // the iterator class associated with this string class + + final private class AttributedStringIterator implements AttributedCharacterIterator { + + // note on synchronization: + // we don't synchronize on the iterator, assuming that an iterator is only used in one thread. + // we do synchronize access to the AttributedString however, since it's more likely to be shared between threads. + + // start and end index for our iteration + private int beginIndex; + private int endIndex; + + // attributes that our client is interested in + private Attribute[] relevantAttributes; + + // the current index for our iteration + // invariant: beginIndex <= currentIndex <= endIndex + private int currentIndex; + + // information about the run that includes currentIndex + private int currentRunIndex; + private int currentRunStart; + private int currentRunLimit; + + // constructor + AttributedStringIterator(Attribute[] attributes, int beginIndex, int endIndex) { + + if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) { + throw new IllegalArgumentException("Invalid substring range"); + } + + this.beginIndex = beginIndex; + this.endIndex = endIndex; + this.currentIndex = beginIndex; + updateRunInfo(); + if (attributes != null) { + relevantAttributes = (Attribute[]) attributes.clone(); + } + } + + // Object methods. See documentation in that class. + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AttributedStringIterator)) { + return false; + } + + AttributedStringIterator that = (AttributedStringIterator) obj; + + if (AttributedString.this != that.getString()) + return false; + if (currentIndex != that.currentIndex || beginIndex != that.beginIndex || endIndex != that.endIndex) + return false; + return true; + } + + public int hashCode() { + return text.hashCode() ^ currentIndex ^ beginIndex ^ endIndex; + } + + public Object clone() { + try { + AttributedStringIterator other = (AttributedStringIterator) super.clone(); + return other; + } + catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + // CharacterIterator methods. See documentation in that interface. + + public char first() { + return internalSetIndex(beginIndex); + } + + public char last() { + if (endIndex == beginIndex) { + return internalSetIndex(endIndex); + } else { + return internalSetIndex(endIndex - 1); + } + } + + public char current() { + if (currentIndex == endIndex) { + return DONE; + } else { + return charAt(currentIndex); + } + } + + public char next() { + if (currentIndex < endIndex) { + return internalSetIndex(currentIndex + 1); + } + else { + return DONE; + } + } + + public char previous() { + if (currentIndex > beginIndex) { + return internalSetIndex(currentIndex - 1); + } + else { + return DONE; + } + } + + public char setIndex(int position) { + if (position < beginIndex || position > endIndex) + throw new IllegalArgumentException("Invalid index"); + return internalSetIndex(position); + } + + public int getBeginIndex() { + return beginIndex; + } + + public int getEndIndex() { + return endIndex; + } + + public int getIndex() { + return currentIndex; + } + + // AttributedCharacterIterator methods. See documentation in that interface. + + public int getRunStart() { + return currentRunStart; + } + + public int getRunStart(Attribute attribute) { + if (currentRunStart == beginIndex || currentRunIndex == -1) { + return currentRunStart; + } else { + Object value = getAttribute(attribute); + int runStart = currentRunStart; + int runIndex = currentRunIndex; + while (runStart > beginIndex && + valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex - 1))) { + runIndex--; + runStart = runStarts[runIndex]; + } + if (runStart < beginIndex) { + runStart = beginIndex; + } + return runStart; + } + } + + public int getRunStart(Set attributes) { + if (currentRunStart == beginIndex || currentRunIndex == -1) { + return currentRunStart; + } else { + int runStart = currentRunStart; + int runIndex = currentRunIndex; + while (runStart > beginIndex && + AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex - 1)) { + runIndex--; + runStart = runStarts[runIndex]; + } + if (runStart < beginIndex) { + runStart = beginIndex; + } + return runStart; + } + } + + public int getRunLimit() { + return currentRunLimit; + } + + public int getRunLimit(Attribute attribute) { + if (currentRunLimit == endIndex || currentRunIndex == -1) { + return currentRunLimit; + } else { + Object value = getAttribute(attribute); + int runLimit = currentRunLimit; + int runIndex = currentRunIndex; + while (runLimit < endIndex && + valuesMatch(value, AttributedString.this.getAttribute(attribute, runIndex + 1))) { + runIndex++; + runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex; + } + if (runLimit > endIndex) { + runLimit = endIndex; + } + return runLimit; + } + } + + public int getRunLimit(Set attributes) { + if (currentRunLimit == endIndex || currentRunIndex == -1) { + return currentRunLimit; + } else { + int runLimit = currentRunLimit; + int runIndex = currentRunIndex; + while (runLimit < endIndex && + AttributedString.this.attributeValuesMatch(attributes, currentRunIndex, runIndex + 1)) { + runIndex++; + runLimit = runIndex < runCount - 1 ? runStarts[runIndex + 1] : endIndex; + } + if (runLimit > endIndex) { + runLimit = endIndex; + } + return runLimit; + } + } + + public Map getAttributes() { + if (runAttributes == null || currentRunIndex == -1 || runAttributes[currentRunIndex] == null) { + // ??? would be nice to return null, but current spec doesn't allow it + // returning Hashtable saves AttributeMap from dealing with emptiness + return new Hashtable(); + } + return new AttributeMap(currentRunIndex, beginIndex, endIndex); + } + + public Set getAllAttributeKeys() { + // ??? This should screen out attribute keys that aren't relevant to the client + if (runAttributes == null) { + // ??? would be nice to return null, but current spec doesn't allow it + // returning HashSet saves us from dealing with emptiness + return new HashSet(); + } + synchronized (AttributedString.this) { + // ??? should try to create this only once, then update if necessary, + // and give callers read-only view + Set keys = new HashSet(); + int i = 0; + while (i < runCount) { + if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) { + Vector currentRunAttributes = runAttributes[i]; + if (currentRunAttributes != null) { + int j = currentRunAttributes.size(); + while (j-- > 0) { + keys.add(currentRunAttributes.get(j)); + } + } + } + i++; + } + return keys; + } + } + + public Object getAttribute(Attribute attribute) { + int runIndex = currentRunIndex; + if (runIndex < 0) { + return null; + } + return AttributedString.this.getAttributeCheckRange(attribute, runIndex, beginIndex, endIndex); + } + + // internally used methods + + private AttributedString getString() { + return AttributedString.this; + } + + // set the current index, update information about the current run if necessary, + // return the character at the current index + private char internalSetIndex(int position) { + currentIndex = position; + if (position < currentRunStart || position >= currentRunLimit) { + updateRunInfo(); + } + if (currentIndex == endIndex) { + return DONE; + } else { + return charAt(position); + } + } + + // update the information about the current run + private void updateRunInfo() { + if (currentIndex == endIndex) { + currentRunStart = currentRunLimit = endIndex; + currentRunIndex = -1; + } else { + synchronized (AttributedString.this) { + int runIndex = -1; + while (runIndex < runCount - 1 && runStarts[runIndex + 1] <= currentIndex) + runIndex++; + currentRunIndex = runIndex; + if (runIndex >= 0) { + currentRunStart = runStarts[runIndex]; + if (currentRunStart < beginIndex) + currentRunStart = beginIndex; + } + else { + currentRunStart = beginIndex; + } + if (runIndex < runCount - 1) { + currentRunLimit = runStarts[runIndex + 1]; + if (currentRunLimit > endIndex) + currentRunLimit = endIndex; + } + else { + currentRunLimit = endIndex; + } + } + } + } + + } + + // the map class associated with this string class, giving access to the attributes of one run + + final private class AttributeMap extends AbstractMap { + + int runIndex; + int beginIndex; + int endIndex; + + AttributeMap(int runIndex, int beginIndex, int endIndex) { + this.runIndex = runIndex; + this.beginIndex = beginIndex; + this.endIndex = endIndex; + } + + public Set entrySet() { + HashSet set = new HashSet(); + synchronized (AttributedString.this) { + int size = runAttributes[runIndex].size(); + for (int i = 0; i < size; i++) { + Attribute key = (Attribute) runAttributes[runIndex].get(i); + Object value = runAttributeValues[runIndex].get(i); + if (value instanceof Annotation) { + value = AttributedString.this.getAttributeCheckRange(key, + runIndex, beginIndex, endIndex); + if (value == null) { + continue; + } + } + Map.Entry entry = new AttributeEntry(key, value); + set.add(entry); + } + } + return set; + } + + public Object get(Object key) { + return AttributedString.this.getAttributeCheckRange((Attribute) key, runIndex, beginIndex, endIndex); + } + } +} + +class AttributeEntry implements Map.Entry { + + private Attribute key; + private Object value; + + AttributeEntry(Attribute key, Object value) { + this.key = key; + this.value = value; + } + + public boolean equals(Object o) { + if (!(o instanceof AttributeEntry)) { + return false; + } + AttributeEntry other = (AttributeEntry) o; + return other.key.equals(key) && + (value == null ? other.value == null : other.value.equals(value)); + } + + public Object getKey() { + return key; + } + + public Object getValue() { + return value; + } + + public Object setValue(Object newValue) { + throw new UnsupportedOperationException(); + } + + public int hashCode() { + return key.hashCode() ^ (value==null ? 0 : value.hashCode()); + } + + public String toString() { + return key.toString()+"="+value.toString(); + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/CalendarBuilder.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/CalendarBuilder.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.text; + +import java.util.Calendar; +import static java.util.GregorianCalendar.*; + +/** + * {@code CalendarBuilder} keeps field-value pairs for setting + * the calendar fields of the given {@code Calendar}. It has the + * {@link Calendar#FIELD_COUNT FIELD_COUNT}-th field for the week year + * support. Also {@code ISO_DAY_OF_WEEK} is used to specify + * {@code DAY_OF_WEEK} in the ISO day of week numbering. + * + *

{@code CalendarBuilder} retains the semantic of the pseudo + * timestamp for fields. {@code CalendarBuilder} uses a single + * int array combining fields[] and stamp[] of {@code Calendar}. + * + * @author Masayoshi Okutsu + */ +class CalendarBuilder { + /* + * Pseudo time stamp constants used in java.util.Calendar + */ + private static final int UNSET = 0; + private static final int COMPUTED = 1; + private static final int MINIMUM_USER_STAMP = 2; + + private static final int MAX_FIELD = FIELD_COUNT + 1; + + public static final int WEEK_YEAR = FIELD_COUNT; + public static final int ISO_DAY_OF_WEEK = 1000; // pseudo field index + + // stamp[] (lower half) and field[] (upper half) combined + private final int[] field; + private int nextStamp; + private int maxFieldIndex; + + CalendarBuilder() { + field = new int[MAX_FIELD * 2]; + nextStamp = MINIMUM_USER_STAMP; + maxFieldIndex = -1; + } + + CalendarBuilder set(int index, int value) { + if (index == ISO_DAY_OF_WEEK) { + index = DAY_OF_WEEK; + value = toCalendarDayOfWeek(value); + } + field[index] = nextStamp++; + field[MAX_FIELD + index] = value; + if (index > maxFieldIndex && index < FIELD_COUNT) { + maxFieldIndex = index; + } + return this; + } + + CalendarBuilder addYear(int value) { + field[MAX_FIELD + YEAR] += value; + field[MAX_FIELD + WEEK_YEAR] += value; + return this; + } + + boolean isSet(int index) { + if (index == ISO_DAY_OF_WEEK) { + index = DAY_OF_WEEK; + } + return field[index] > UNSET; + } + + Calendar establish(Calendar cal) { + boolean weekDate = isSet(WEEK_YEAR) + && field[WEEK_YEAR] > field[YEAR]; + if (weekDate && !cal.isWeekDateSupported()) { + // Use YEAR instead + if (!isSet(YEAR)) { + set(YEAR, field[MAX_FIELD + WEEK_YEAR]); + } + weekDate = false; + } + + cal.clear(); + // Set the fields from the min stamp to the max stamp so that + // the field resolution works in the Calendar. + for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) { + for (int index = 0; index <= maxFieldIndex; index++) { + if (field[index] == stamp) { + cal.set(index, field[MAX_FIELD + index]); + break; + } + } + } + + if (weekDate) { + int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1; + int dayOfWeek = isSet(DAY_OF_WEEK) ? + field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek(); + if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) { + if (dayOfWeek >= 8) { + dayOfWeek--; + weekOfYear += dayOfWeek / 7; + dayOfWeek = (dayOfWeek % 7) + 1; + } else { + while (dayOfWeek <= 0) { + dayOfWeek += 7; + weekOfYear--; + } + } + dayOfWeek = toCalendarDayOfWeek(dayOfWeek); + } + cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek); + } + return cal; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("CalendarBuilder:["); + for (int i = 0; i < field.length; i++) { + if (isSet(i)) { + sb.append(i).append('=').append(field[MAX_FIELD + i]).append(','); + } + } + int lastIndex = sb.length() - 1; + if (sb.charAt(lastIndex) == ',') { + sb.setLength(lastIndex); + } + sb.append(']'); + return sb.toString(); + } + + static int toISODayOfWeek(int calendarDayOfWeek) { + return calendarDayOfWeek == SUNDAY ? 7 : calendarDayOfWeek - 1; + } + + static int toCalendarDayOfWeek(int isoDayOfWeek) { + if (!isValidDayOfWeek(isoDayOfWeek)) { + // adjust later for lenient mode + return isoDayOfWeek; + } + return isoDayOfWeek == 7 ? SUNDAY : isoDayOfWeek + 1; + } + + static boolean isValidDayOfWeek(int dayOfWeek) { + return dayOfWeek > 0 && dayOfWeek <= 7; + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/CharacterIterator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/CharacterIterator.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,193 @@ +/* + * Copyright (c) 1996, 2000, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + + +/** + * This interface defines a protocol for bidirectional iteration over text. + * The iterator iterates over a bounded sequence of characters. Characters + * are indexed with values beginning with the value returned by getBeginIndex() and + * continuing through the value returned by getEndIndex()-1. + *

+ * Iterators maintain a current character index, whose valid range is from + * getBeginIndex() to getEndIndex(); the value getEndIndex() is included to allow + * handling of zero-length text ranges and for historical reasons. + * The current index can be retrieved by calling getIndex() and set directly + * by calling setIndex(), first(), and last(). + *

+ * The methods previous() and next() are used for iteration. They return DONE if + * they would move outside the range from getBeginIndex() to getEndIndex() -1, + * signaling that the iterator has reached the end of the sequence. DONE is + * also returned by other methods to indicate that the current index is + * outside this range. + * + *

Examples:

+ * + * Traverse the text from start to finish + *

+ * public void traverseForward(CharacterIterator iter) {
+ *     for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
+ *         processChar(c);
+ *     }
+ * }
+ * 
+ * + * Traverse the text backwards, from end to start + *
+ * public void traverseBackward(CharacterIterator iter) {
+ *     for(char c = iter.last(); c != CharacterIterator.DONE; c = iter.previous()) {
+ *         processChar(c);
+ *     }
+ * }
+ * 
+ * + * Traverse both forward and backward from a given position in the text. + * Calls to notBoundary() in this example represents some + * additional stopping criteria. + *
+ * public void traverseOut(CharacterIterator iter, int pos) {
+ *     for (char c = iter.setIndex(pos);
+ *              c != CharacterIterator.DONE && notBoundary(c);
+ *              c = iter.next()) {
+ *     }
+ *     int end = iter.getIndex();
+ *     for (char c = iter.setIndex(pos);
+ *             c != CharacterIterator.DONE && notBoundary(c);
+ *             c = iter.previous()) {
+ *     }
+ *     int start = iter.getIndex();
+ *     processSection(start, end);
+ * }
+ * 
+ * + * @see StringCharacterIterator + * @see AttributedCharacterIterator + */ + +public interface CharacterIterator extends Cloneable +{ + + /** + * Constant that is returned when the iterator has reached either the end + * or the beginning of the text. The value is '\\uFFFF', the "not a + * character" value which should not occur in any valid Unicode string. + */ + public static final char DONE = '\uFFFF'; + + /** + * Sets the position to getBeginIndex() and returns the character at that + * position. + * @return the first character in the text, or DONE if the text is empty + * @see #getBeginIndex() + */ + public char first(); + + /** + * Sets the position to getEndIndex()-1 (getEndIndex() if the text is empty) + * and returns the character at that position. + * @return the last character in the text, or DONE if the text is empty + * @see #getEndIndex() + */ + public char last(); + + /** + * Gets the character at the current position (as returned by getIndex()). + * @return the character at the current position or DONE if the current + * position is off the end of the text. + * @see #getIndex() + */ + public char current(); + + /** + * Increments the iterator's index by one and returns the character + * at the new index. If the resulting index is greater or equal + * to getEndIndex(), the current index is reset to getEndIndex() and + * a value of DONE is returned. + * @return the character at the new position or DONE if the new + * position is off the end of the text range. + */ + public char next(); + + /** + * Decrements the iterator's index by one and returns the character + * at the new index. If the current index is getBeginIndex(), the index + * remains at getBeginIndex() and a value of DONE is returned. + * @return the character at the new position or DONE if the current + * position is equal to getBeginIndex(). + */ + public char previous(); + + /** + * Sets the position to the specified position in the text and returns that + * character. + * @param position the position within the text. Valid values range from + * getBeginIndex() to getEndIndex(). An IllegalArgumentException is thrown + * if an invalid value is supplied. + * @return the character at the specified position or DONE if the specified position is equal to getEndIndex() + */ + public char setIndex(int position); + + /** + * Returns the start index of the text. + * @return the index at which the text begins. + */ + public int getBeginIndex(); + + /** + * Returns the end index of the text. This index is the index of the first + * character following the end of the text. + * @return the index after the last character in the text + */ + public int getEndIndex(); + + /** + * Returns the current index. + * @return the current index. + */ + public int getIndex(); + + /** + * Create a copy of this iterator + * @return A copy of this + */ + public Object clone(); + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/CharacterIteratorFieldDelegate.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/CharacterIteratorFieldDelegate.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.text; + +import java.util.ArrayList; + +/** + * CharacterIteratorFieldDelegate combines the notifications from a Format + * into a resulting AttributedCharacterIterator. The resulting + * AttributedCharacterIterator can be retrieved by way of + * the getIterator method. + * + */ +class CharacterIteratorFieldDelegate implements Format.FieldDelegate { + /** + * Array of AttributeStrings. Whenever formatted is invoked + * for a region > size, a new instance of AttributedString is added to + * attributedStrings. Subsequent invocations of formatted + * for existing regions result in invoking addAttribute on the existing + * AttributedStrings. + */ + private ArrayList attributedStrings; + /** + * Running count of the number of characters that have + * been encountered. + */ + private int size; + + + CharacterIteratorFieldDelegate() { + attributedStrings = new ArrayList(); + } + + public void formatted(Format.Field attr, Object value, int start, int end, + StringBuffer buffer) { + if (start != end) { + if (start < size) { + // Adjust attributes of existing runs + int index = size; + int asIndex = attributedStrings.size() - 1; + + while (start < index) { + AttributedString as = (AttributedString)attributedStrings. + get(asIndex--); + int newIndex = index - as.length(); + int aStart = Math.max(0, start - newIndex); + + as.addAttribute(attr, value, aStart, Math.min( + end - start, as.length() - aStart) + + aStart); + index = newIndex; + } + } + if (size < start) { + // Pad attributes + attributedStrings.add(new AttributedString( + buffer.substring(size, start))); + size = start; + } + if (size < end) { + // Add new string + int aStart = Math.max(start, size); + AttributedString string = new AttributedString( + buffer.substring(aStart, end)); + + string.addAttribute(attr, value); + attributedStrings.add(string); + size = end; + } + } + } + + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer) { + formatted(attr, value, start, end, buffer); + } + + /** + * Returns an AttributedCharacterIterator that can be used + * to iterate over the resulting formatted String. + * + * @pararm string Result of formatting. + */ + public AttributedCharacterIterator getIterator(String string) { + // Add the last AttributedCharacterIterator if necessary + // assert(size <= string.length()); + if (string.length() > size) { + attributedStrings.add(new AttributedString( + string.substring(size))); + size = string.length(); + } + int iCount = attributedStrings.size(); + AttributedCharacterIterator iterators[] = new + AttributedCharacterIterator[iCount]; + + for (int counter = 0; counter < iCount; counter++) { + iterators[counter] = ((AttributedString)attributedStrings. + get(counter)).getIterator(); + } + return new AttributedString(iterators).getIterator(); + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/ChoiceFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/ChoiceFormat.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,619 @@ +/* + * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.Arrays; + +/** + * A ChoiceFormat allows you to attach a format to a range of numbers. + * It is generally used in a MessageFormat for handling plurals. + * The choice is specified with an ascending list of doubles, where each item + * specifies a half-open interval up to the next item: + *
+ *
+ * X matches j if and only if limit[j] <= X < limit[j+1]
+ * 
+ *
+ * If there is no match, then either the first or last index is used, depending + * on whether the number (X) is too low or too high. If the limit array is not + * in ascending order, the results of formatting will be incorrect. ChoiceFormat + * also accepts \u221E as equivalent to infinity(INF). + * + *

+ * Note: + * ChoiceFormat differs from the other Format + * classes in that you create a ChoiceFormat object with a + * constructor (not with a getInstance style factory + * method). The factory methods aren't necessary because ChoiceFormat + * doesn't require any complex setup for a given locale. In fact, + * ChoiceFormat doesn't implement any locale specific behavior. + * + *

+ * When creating a ChoiceFormat, you must specify an array of formats + * and an array of limits. The length of these arrays must be the same. + * For example, + *

    + *
  • + * limits = {1,2,3,4,5,6,7}
    + * formats = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"} + *
  • + * limits = {0, 1, ChoiceFormat.nextDouble(1)}
    + * formats = {"no files", "one file", "many files"}
    + * (nextDouble can be used to get the next higher double, to + * make the half-open interval.) + *
+ * + *

+ * Here is a simple example that shows formatting and parsing: + *

+ *
+ * double[] limits = {1,2,3,4,5,6,7};
+ * String[] dayOfWeekNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"};
+ * ChoiceFormat form = new ChoiceFormat(limits, dayOfWeekNames);
+ * ParsePosition status = new ParsePosition(0);
+ * for (double i = 0.0; i <= 8.0; ++i) {
+ *     status.setIndex(0);
+ *     System.out.println(i + " -> " + form.format(i) + " -> "
+ *                              + form.parse(form.format(i),status));
+ * }
+ * 
+ *
+ * Here is a more complex example, with a pattern format: + *
+ *
+ * double[] filelimits = {0,1,2};
+ * String[] filepart = {"are no files","is one file","are {2} files"};
+ * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
+ * Format[] testFormats = {fileform, null, NumberFormat.getInstance()};
+ * MessageFormat pattform = new MessageFormat("There {0} on {1}");
+ * pattform.setFormats(testFormats);
+ * Object[] testArgs = {null, "ADisk", null};
+ * for (int i = 0; i < 4; ++i) {
+ *     testArgs[0] = new Integer(i);
+ *     testArgs[2] = testArgs[0];
+ *     System.out.println(pattform.format(testArgs));
+ * }
+ * 
+ *
+ *

+ * Specifying a pattern for ChoiceFormat objects is fairly straightforward. + * For example: + *

+ *
+ * ChoiceFormat fmt = new ChoiceFormat(
+ *      "-1#is negative| 0#is zero or fraction | 1#is one |1.0<is 1+ |2#is two |2<is more than 2.");
+ * System.out.println("Formatter Pattern : " + fmt.toPattern());
+ *
+ * System.out.println("Format with -INF : " + fmt.format(Double.NEGATIVE_INFINITY));
+ * System.out.println("Format with -1.0 : " + fmt.format(-1.0));
+ * System.out.println("Format with 0 : " + fmt.format(0));
+ * System.out.println("Format with 0.9 : " + fmt.format(0.9));
+ * System.out.println("Format with 1.0 : " + fmt.format(1));
+ * System.out.println("Format with 1.5 : " + fmt.format(1.5));
+ * System.out.println("Format with 2 : " + fmt.format(2));
+ * System.out.println("Format with 2.1 : " + fmt.format(2.1));
+ * System.out.println("Format with NaN : " + fmt.format(Double.NaN));
+ * System.out.println("Format with +INF : " + fmt.format(Double.POSITIVE_INFINITY));
+ * 
+ *
+ * And the output result would be like the following: + *
+ *
+ *   Format with -INF : is negative
+ *   Format with -1.0 : is negative
+ *   Format with 0 : is zero or fraction
+ *   Format with 0.9 : is zero or fraction
+ *   Format with 1.0 : is one
+ *   Format with 1.5 : is 1+
+ *   Format with 2 : is two
+ *   Format with 2.1 : is more than 2.
+ *   Format with NaN : is negative
+ *   Format with +INF : is more than 2.
+ * 
+ *
+ * + *

Synchronization

+ * + *

+ * Choice formats are not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * + * @see DecimalFormat + * @see MessageFormat + * @author Mark Davis + */ +public class ChoiceFormat extends NumberFormat { + + // Proclaim serial compatibility with 1.1 FCS + private static final long serialVersionUID = 1795184449645032964L; + + /** + * Sets the pattern. + * @param newPattern See the class description. + */ + public void applyPattern(String newPattern) { + StringBuffer[] segments = new StringBuffer[2]; + for (int i = 0; i < segments.length; ++i) { + segments[i] = new StringBuffer(); + } + double[] newChoiceLimits = new double[30]; + String[] newChoiceFormats = new String[30]; + int count = 0; + int part = 0; + double startValue = 0; + double oldStartValue = Double.NaN; + boolean inQuote = false; + for (int i = 0; i < newPattern.length(); ++i) { + char ch = newPattern.charAt(i); + if (ch=='\'') { + // Check for "''" indicating a literal quote + if ((i+1)= 0 + || text.indexOf('#') >= 0 + || text.indexOf('\u2264') >= 0 + || text.indexOf('|') >= 0; + if (needQuote) result.append('\''); + if (text.indexOf('\'') < 0) result.append(text); + else { + for (int j=0; jformat(double, StringBuffer, FieldPosition) + * thus the range of longs that are supported is only equal to + * the range that can be stored by double. This will never be + * a practical limitation. + */ + public StringBuffer format(long number, StringBuffer toAppendTo, + FieldPosition status) { + return format((double)number, toAppendTo, status); + } + + /** + * Returns pattern with formatted double. + * @param number number to be formatted & substituted. + * @param toAppendTo where text is appended. + * @param status ignore no useful status is returned. + */ + public StringBuffer format(double number, StringBuffer toAppendTo, + FieldPosition status) { + // find the number + int i; + for (i = 0; i < choiceLimits.length; ++i) { + if (!(number >= choiceLimits[i])) { + // same as number < choiceLimits, except catchs NaN + break; + } + } + --i; + if (i < 0) i = 0; + // return either a formatted number, or a string + return toAppendTo.append(choiceFormats[i]); + } + + /** + * Parses a Number from the input text. + * @param text the source text. + * @param status an input-output parameter. On input, the + * status.index field indicates the first character of the + * source text that should be parsed. On exit, if no error + * occured, status.index is set to the first unparsed character + * in the source text. On exit, if an error did occur, + * status.index is unchanged and status.errorIndex is set to the + * first index of the character that caused the parse to fail. + * @return A Number representing the value of the number parsed. + */ + public Number parse(String text, ParsePosition status) { + // find the best number (defined as the one with the longest parse) + int start = status.index; + int furthest = start; + double bestNumber = Double.NaN; + double tempNumber = 0.0; + for (int i = 0; i < choiceFormats.length; ++i) { + String tempString = choiceFormats[i]; + if (text.regionMatches(start, tempString, 0, tempString.length())) { + status.index = start + tempString.length(); + tempNumber = choiceLimits[i]; + if (status.index > furthest) { + furthest = status.index; + bestNumber = tempNumber; + if (furthest == text.length()) break; + } + } + } + status.index = furthest; + if (status.index == start) { + status.errorIndex = furthest; + } + return new Double(bestNumber); + } + + /** + * Finds the least double greater than d. + * If NaN, returns same value. + *

Used to make half-open intervals. + * @see #previousDouble + */ + public static final double nextDouble (double d) { + return nextDouble(d,true); + } + + /** + * Finds the greatest double less than d. + * If NaN, returns same value. + * @see #nextDouble + */ + public static final double previousDouble (double d) { + return nextDouble(d,false); + } + + /** + * Overrides Cloneable + */ + public Object clone() + { + ChoiceFormat other = (ChoiceFormat) super.clone(); + // for primitives or immutables, shallow clone is enough + other.choiceLimits = (double[]) choiceLimits.clone(); + other.choiceFormats = (String[]) choiceFormats.clone(); + return other; + } + + /** + * Generates a hash code for the message format object. + */ + public int hashCode() { + int result = choiceLimits.length; + if (choiceFormats.length > 0) { + // enough for reasonable distribution + result ^= choiceFormats[choiceFormats.length-1].hashCode(); + } + return result; + } + + /** + * Equality comparision between two + */ + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) // quick check + return true; + if (getClass() != obj.getClass()) + return false; + ChoiceFormat other = (ChoiceFormat) obj; + return (Arrays.equals(choiceLimits, other.choiceLimits) + && Arrays.equals(choiceFormats, other.choiceFormats)); + } + + /** + * After reading an object from the input stream, do a simple verification + * to maintain class invariants. + * @throws InvalidObjectException if the objects read from the stream is invalid. + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + if (choiceLimits.length != choiceFormats.length) { + throw new InvalidObjectException( + "limits and format arrays of different length."); + } + } + + // ===============privates=========================== + + /** + * A list of lower bounds for the choices. The formatter will return + * choiceFormats[i] if the number being formatted is greater than or equal to + * choiceLimits[i] and less than choiceLimits[i+1]. + * @serial + */ + private double[] choiceLimits; + + /** + * A list of choice strings. The formatter will return + * choiceFormats[i] if the number being formatted is greater than or equal to + * choiceLimits[i] and less than choiceLimits[i+1]. + * @serial + */ + private String[] choiceFormats; + + /* + static final long SIGN = 0x8000000000000000L; + static final long EXPONENT = 0x7FF0000000000000L; + static final long SIGNIFICAND = 0x000FFFFFFFFFFFFFL; + + private static double nextDouble (double d, boolean positive) { + if (Double.isNaN(d) || Double.isInfinite(d)) { + return d; + } + long bits = Double.doubleToLongBits(d); + long significand = bits & SIGNIFICAND; + if (bits < 0) { + significand |= (SIGN | EXPONENT); + } + long exponent = bits & EXPONENT; + if (positive) { + significand += 1; + // FIXME fix overflow & underflow + } else { + significand -= 1; + // FIXME fix overflow & underflow + } + bits = exponent | (significand & ~EXPONENT); + return Double.longBitsToDouble(bits); + } + */ + + static final long SIGN = 0x8000000000000000L; + static final long EXPONENT = 0x7FF0000000000000L; + static final long POSITIVEINFINITY = 0x7FF0000000000000L; + + /** + * Finds the least double greater than d (if positive == true), + * or the greatest double less than d (if positive == false). + * If NaN, returns same value. + * + * Does not affect floating-point flags, + * provided these member functions do not: + * Double.longBitsToDouble(long) + * Double.doubleToLongBits(double) + * Double.isNaN(double) + */ + public static double nextDouble (double d, boolean positive) { + + /* filter out NaN's */ + if (Double.isNaN(d)) { + return d; + } + + /* zero's are also a special case */ + if (d == 0.0) { + double smallestPositiveDouble = Double.longBitsToDouble(1L); + if (positive) { + return smallestPositiveDouble; + } else { + return -smallestPositiveDouble; + } + } + + /* if entering here, d is a nonzero value */ + + /* hold all bits in a long for later use */ + long bits = Double.doubleToLongBits(d); + + /* strip off the sign bit */ + long magnitude = bits & ~SIGN; + + /* if next double away from zero, increase magnitude */ + if ((bits > 0) == positive) { + if (magnitude != POSITIVEINFINITY) { + magnitude += 1; + } + } + /* else decrease magnitude */ + else { + magnitude -= 1; + } + + /* restore sign bit and return */ + long signbit = bits & SIGN; + return Double.longBitsToDouble (magnitude | signbit); + } + + private static double[] doubleArraySize(double[] array) { + int oldSize = array.length; + double[] newArray = new double[oldSize * 2]; + System.arraycopy(array, 0, newArray, 0, oldSize); + return newArray; + } + + private String[] doubleArraySize(String[] array) { + int oldSize = array.length; + String[] newArray = new String[oldSize * 2]; + System.arraycopy(array, 0, newArray, 0, oldSize); + return newArray; + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/DateFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DateFormat.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,1030 @@ +/* + * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.text.spi.DateFormatProvider; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.TimeZone; +import java.util.spi.LocaleServiceProvider; +import sun.util.LocaleServiceProviderPool; + +/** + * {@code DateFormat} is an abstract class for date/time formatting subclasses which + * formats and parses dates or time in a language-independent manner. + * The date/time formatting subclass, such as {@link SimpleDateFormat}, allows for + * formatting (i.e., date -> text), parsing (text -> date), and + * normalization. The date is represented as a Date object or + * as the milliseconds since January 1, 1970, 00:00:00 GMT. + * + *

{@code DateFormat} provides many class methods for obtaining default date/time + * formatters based on the default or a given locale and a number of formatting + * styles. The formatting styles include {@link #FULL}, {@link #LONG}, {@link #MEDIUM}, and {@link #SHORT}. More + * detail and examples of using these styles are provided in the method + * descriptions. + * + *

{@code DateFormat} helps you to format and parse dates for any locale. + * Your code can be completely independent of the locale conventions for + * months, days of the week, or even the calendar format: lunar vs. solar. + * + *

To format a date for the current Locale, use one of the + * static factory methods: + *

+ *  myString = DateFormat.getDateInstance().format(myDate);
+ * 
+ *

If you are formatting multiple dates, it is + * more efficient to get the format and use it multiple times so that + * the system doesn't have to fetch the information about the local + * language and country conventions multiple times. + *

+ *  DateFormat df = DateFormat.getDateInstance();
+ *  for (int i = 0; i < myDate.length; ++i) {
+ *    output.println(df.format(myDate[i]) + "; ");
+ *  }
+ * 
+ *

To format a date for a different Locale, specify it in the + * call to {@link #getDateInstance(int, Locale) getDateInstance()}. + *

+ *  DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
+ * 
+ *

You can use a DateFormat to parse also. + *

+ *  myDate = df.parse(myString);
+ * 
+ *

Use {@code getDateInstance} to get the normal date format for that country. + * There are other static factory methods available. + * Use {@code getTimeInstance} to get the time format for that country. + * Use {@code getDateTimeInstance} to get a date and time format. You can pass in + * different options to these factory methods to control the length of the + * result; from {@link #SHORT} to {@link #MEDIUM} to {@link #LONG} to {@link #FULL}. The exact result depends + * on the locale, but generally: + *

  • {@link #SHORT} is completely numeric, such as {@code 12.13.52} or {@code 3:30pm} + *
  • {@link #MEDIUM} is longer, such as {@code Jan 12, 1952} + *
  • {@link #LONG} is longer, such as {@code January 12, 1952} or {@code 3:30:32pm} + *
  • {@link #FULL} is pretty completely specified, such as + * {@code Tuesday, April 12, 1952 AD or 3:30:42pm PST}. + *
+ * + *

You can also set the time zone on the format if you wish. + * If you want even more control over the format or parsing, + * (or want to give your users more control), + * you can try casting the {@code DateFormat} you get from the factory methods + * to a {@link SimpleDateFormat}. This will work for the majority + * of countries; just remember to put it in a {@code try} block in case you + * encounter an unusual one. + * + *

You can also use forms of the parse and format methods with + * {@link ParsePosition} and {@link FieldPosition} to + * allow you to + *

  • progressively parse through pieces of a string. + *
  • align any particular field, or find out where it is for selection + * on the screen. + *
+ * + *

Synchronization

+ * + *

+ * Date formats are not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see Format + * @see NumberFormat + * @see SimpleDateFormat + * @see java.util.Calendar + * @see java.util.GregorianCalendar + * @see java.util.TimeZone + * @author Mark Davis, Chen-Lieh Huang, Alan Liu + */ +public abstract class DateFormat extends Format { + + /** + * The {@link Calendar} instance used for calculating the date-time fields + * and the instant of time. This field is used for both formatting and + * parsing. + * + *

Subclasses should initialize this field to a {@link Calendar} + * appropriate for the {@link Locale} associated with this + * DateFormat. + * @serial + */ + protected Calendar calendar; + + /** + * The number formatter that DateFormat uses to format numbers + * in dates and times. Subclasses should initialize this to a number format + * appropriate for the locale associated with this DateFormat. + * @serial + */ + protected NumberFormat numberFormat; + + /** + * Useful constant for ERA field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int ERA_FIELD = 0; + /** + * Useful constant for YEAR field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int YEAR_FIELD = 1; + /** + * Useful constant for MONTH field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int MONTH_FIELD = 2; + /** + * Useful constant for DATE field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int DATE_FIELD = 3; + /** + * Useful constant for one-based HOUR_OF_DAY field alignment. + * Used in FieldPosition of date/time formatting. + * HOUR_OF_DAY1_FIELD is used for the one-based 24-hour clock. + * For example, 23:59 + 01:00 results in 24:59. + */ + public final static int HOUR_OF_DAY1_FIELD = 4; + /** + * Useful constant for zero-based HOUR_OF_DAY field alignment. + * Used in FieldPosition of date/time formatting. + * HOUR_OF_DAY0_FIELD is used for the zero-based 24-hour clock. + * For example, 23:59 + 01:00 results in 00:59. + */ + public final static int HOUR_OF_DAY0_FIELD = 5; + /** + * Useful constant for MINUTE field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int MINUTE_FIELD = 6; + /** + * Useful constant for SECOND field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int SECOND_FIELD = 7; + /** + * Useful constant for MILLISECOND field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int MILLISECOND_FIELD = 8; + /** + * Useful constant for DAY_OF_WEEK field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int DAY_OF_WEEK_FIELD = 9; + /** + * Useful constant for DAY_OF_YEAR field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int DAY_OF_YEAR_FIELD = 10; + /** + * Useful constant for DAY_OF_WEEK_IN_MONTH field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int DAY_OF_WEEK_IN_MONTH_FIELD = 11; + /** + * Useful constant for WEEK_OF_YEAR field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int WEEK_OF_YEAR_FIELD = 12; + /** + * Useful constant for WEEK_OF_MONTH field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int WEEK_OF_MONTH_FIELD = 13; + /** + * Useful constant for AM_PM field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int AM_PM_FIELD = 14; + /** + * Useful constant for one-based HOUR field alignment. + * Used in FieldPosition of date/time formatting. + * HOUR1_FIELD is used for the one-based 12-hour clock. + * For example, 11:30 PM + 1 hour results in 12:30 AM. + */ + public final static int HOUR1_FIELD = 15; + /** + * Useful constant for zero-based HOUR field alignment. + * Used in FieldPosition of date/time formatting. + * HOUR0_FIELD is used for the zero-based 12-hour clock. + * For example, 11:30 PM + 1 hour results in 00:30 AM. + */ + public final static int HOUR0_FIELD = 16; + /** + * Useful constant for TIMEZONE field alignment. + * Used in FieldPosition of date/time formatting. + */ + public final static int TIMEZONE_FIELD = 17; + + // Proclaim serial compatibility with 1.1 FCS + private static final long serialVersionUID = 7218322306649953788L; + + /** + * Overrides Format. + * Formats a time object into a time string. Examples of time objects + * are a time value expressed in milliseconds and a Date object. + * @param obj must be a Number or a Date. + * @param toAppendTo the string buffer for the returning time string. + * @return the string buffer passed in as toAppendTo, with formatted text appended. + * @param fieldPosition keeps track of the position of the field + * within the returned string. + * On input: an alignment field, + * if desired. On output: the offsets of the alignment field. For + * example, given a time text "1996.07.10 AD at 15:08:56 PDT", + * if the given fieldPosition is DateFormat.YEAR_FIELD, the + * begin index and end index of fieldPosition will be set to + * 0 and 4, respectively. + * Notice that if the same time field appears + * more than once in a pattern, the fieldPosition will be set for the first + * occurrence of that time field. For instance, formatting a Date to + * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern + * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD, + * the begin index and end index of fieldPosition will be set to + * 5 and 8, respectively, for the first occurrence of the timezone + * pattern character 'z'. + * @see java.text.Format + */ + public final StringBuffer format(Object obj, StringBuffer toAppendTo, + FieldPosition fieldPosition) + { + if (obj instanceof Date) + return format( (Date)obj, toAppendTo, fieldPosition ); + else if (obj instanceof Number) + return format( new Date(((Number)obj).longValue()), + toAppendTo, fieldPosition ); + else + throw new IllegalArgumentException("Cannot format given Object as a Date"); + } + + /** + * Formats a Date into a date/time string. + * @param date a Date to be formatted into a date/time string. + * @param toAppendTo the string buffer for the returning date/time string. + * @param fieldPosition keeps track of the position of the field + * within the returned string. + * On input: an alignment field, + * if desired. On output: the offsets of the alignment field. For + * example, given a time text "1996.07.10 AD at 15:08:56 PDT", + * if the given fieldPosition is DateFormat.YEAR_FIELD, the + * begin index and end index of fieldPosition will be set to + * 0 and 4, respectively. + * Notice that if the same time field appears + * more than once in a pattern, the fieldPosition will be set for the first + * occurrence of that time field. For instance, formatting a Date to + * the time string "1 PM PDT (Pacific Daylight Time)" using the pattern + * "h a z (zzzz)" and the alignment field DateFormat.TIMEZONE_FIELD, + * the begin index and end index of fieldPosition will be set to + * 5 and 8, respectively, for the first occurrence of the timezone + * pattern character 'z'. + * @return the string buffer passed in as toAppendTo, with formatted text appended. + */ + public abstract StringBuffer format(Date date, StringBuffer toAppendTo, + FieldPosition fieldPosition); + + /** + * Formats a Date into a date/time string. + * @param date the time value to be formatted into a time string. + * @return the formatted time string. + */ + public final String format(Date date) + { + return format(date, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } + + /** + * Parses text from the beginning of the given string to produce a date. + * The method may not use the entire text of the given string. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on date parsing. + * + * @param source A String whose beginning should be parsed. + * @return A Date parsed from the string. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public Date parse(String source) throws ParseException + { + ParsePosition pos = new ParsePosition(0); + Date result = parse(source, pos); + if (pos.index == 0) + throw new ParseException("Unparseable date: \"" + source + "\"" , + pos.errorIndex); + return result; + } + + /** + * Parse a date/time string according to the given parse position. For + * example, a time text {@code "07/10/96 4:5 PM, PDT"} will be parsed into a {@code Date} + * that is equivalent to {@code Date(837039900000L)}. + * + *

By default, parsing is lenient: If the input is not in the form used + * by this object's format method but can still be parsed as a date, then + * the parse succeeds. Clients may insist on strict adherence to the + * format by calling {@link #setLenient(boolean) setLenient(false)}. + * + *

This parsing operation uses the {@link #calendar} to produce + * a {@code Date}. As a result, the {@code calendar}'s date-time + * fields and the {@code TimeZone} value may have been + * overwritten, depending on subclass implementations. Any {@code + * TimeZone} value that has previously been set by a call to + * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need + * to be restored for further operations. + * + * @param source The date/time string to be parsed + * + * @param pos On input, the position at which to start parsing; on + * output, the position at which parsing terminated, or the + * start position if the parse failed. + * + * @return A {@code Date}, or {@code null} if the input could not be parsed + */ + public abstract Date parse(String source, ParsePosition pos); + + /** + * Parses text from a string to produce a Date. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * date is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on date parsing. + * + * @param source A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return A Date parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if pos is null. + */ + public Object parseObject(String source, ParsePosition pos) { + return parse(source, pos); + } + + /** + * Constant for full style pattern. + */ + public static final int FULL = 0; + /** + * Constant for long style pattern. + */ + public static final int LONG = 1; + /** + * Constant for medium style pattern. + */ + public static final int MEDIUM = 2; + /** + * Constant for short style pattern. + */ + public static final int SHORT = 3; + /** + * Constant for default style pattern. Its value is MEDIUM. + */ + public static final int DEFAULT = MEDIUM; + + /** + * Gets the time formatter with the default formatting style + * for the default locale. + * @return a time formatter. + */ + public final static DateFormat getTimeInstance() + { + return get(DEFAULT, 0, 1, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the time formatter with the given formatting style + * for the default locale. + * @param style the given formatting style. For example, + * SHORT for "h:mm a" in the US locale. + * @return a time formatter. + */ + public final static DateFormat getTimeInstance(int style) + { + return get(style, 0, 1, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the time formatter with the given formatting style + * for the given locale. + * @param style the given formatting style. For example, + * SHORT for "h:mm a" in the US locale. + * @param aLocale the given locale. + * @return a time formatter. + */ + public final static DateFormat getTimeInstance(int style, + Locale aLocale) + { + return get(style, 0, 1, aLocale); + } + + /** + * Gets the date formatter with the default formatting style + * for the default locale. + * @return a date formatter. + */ + public final static DateFormat getDateInstance() + { + return get(0, DEFAULT, 2, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the date formatter with the given formatting style + * for the default locale. + * @param style the given formatting style. For example, + * SHORT for "M/d/yy" in the US locale. + * @return a date formatter. + */ + public final static DateFormat getDateInstance(int style) + { + return get(0, style, 2, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the date formatter with the given formatting style + * for the given locale. + * @param style the given formatting style. For example, + * SHORT for "M/d/yy" in the US locale. + * @param aLocale the given locale. + * @return a date formatter. + */ + public final static DateFormat getDateInstance(int style, + Locale aLocale) + { + return get(0, style, 2, aLocale); + } + + /** + * Gets the date/time formatter with the default formatting style + * for the default locale. + * @return a date/time formatter. + */ + public final static DateFormat getDateTimeInstance() + { + return get(DEFAULT, DEFAULT, 3, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the date/time formatter with the given date and time + * formatting styles for the default locale. + * @param dateStyle the given date formatting style. For example, + * SHORT for "M/d/yy" in the US locale. + * @param timeStyle the given time formatting style. For example, + * SHORT for "h:mm a" in the US locale. + * @return a date/time formatter. + */ + public final static DateFormat getDateTimeInstance(int dateStyle, + int timeStyle) + { + return get(timeStyle, dateStyle, 3, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the date/time formatter with the given formatting styles + * for the given locale. + * @param dateStyle the given date formatting style. + * @param timeStyle the given time formatting style. + * @param aLocale the given locale. + * @return a date/time formatter. + */ + public final static DateFormat + getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale) + { + return get(timeStyle, dateStyle, 3, aLocale); + } + + /** + * Get a default date/time formatter that uses the SHORT style for both the + * date and the time. + */ + public final static DateFormat getInstance() { + return getDateTimeInstance(SHORT, SHORT); + } + + /** + * Returns an array of all locales for which the + * get*Instance methods of this class can return + * localized instances. + * The returned array represents the union of locales supported by the Java + * runtime and by installed + * {@link java.text.spi.DateFormatProvider DateFormatProvider} implementations. + * It must contain at least a Locale instance equal to + * {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * DateFormat instances are available. + */ + public static Locale[] getAvailableLocales() + { + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(DateFormatProvider.class); + return pool.getAvailableLocales(); + } + + /** + * Set the calendar to be used by this date format. Initially, the default + * calendar for the specified or default locale is used. + * + *

Any {@link java.util.TimeZone TimeZone} and {@linkplain + * #isLenient() leniency} values that have previously been set are + * overwritten by {@code newCalendar}'s values. + * + * @param newCalendar the new {@code Calendar} to be used by the date format + */ + public void setCalendar(Calendar newCalendar) + { + this.calendar = newCalendar; + } + + /** + * Gets the calendar associated with this date/time formatter. + * + * @return the calendar associated with this date/time formatter. + */ + public Calendar getCalendar() + { + return calendar; + } + + /** + * Allows you to set the number formatter. + * @param newNumberFormat the given new NumberFormat. + */ + public void setNumberFormat(NumberFormat newNumberFormat) + { + this.numberFormat = newNumberFormat; + } + + /** + * Gets the number formatter which this date/time formatter uses to + * format and parse a time. + * @return the number formatter which this date/time formatter uses. + */ + public NumberFormat getNumberFormat() + { + return numberFormat; + } + + /** + * Sets the time zone for the calendar of this {@code DateFormat} object. + * This method is equivalent to the following call. + *

+     *  getCalendar().setTimeZone(zone)
+     * 
+ * + *

The {@code TimeZone} set by this method is overwritten by a + * {@link #setCalendar(java.util.Calendar) setCalendar} call. + * + *

The {@code TimeZone} set by this method may be overwritten as + * a result of a call to the parse method. + * + * @param zone the given new time zone. + */ + public void setTimeZone(TimeZone zone) + { + calendar.setTimeZone(zone); + } + + /** + * Gets the time zone. + * This method is equivalent to the following call. + *

+     *  getCalendar().getTimeZone()
+     * 
+ * + * @return the time zone associated with the calendar of DateFormat. + */ + public TimeZone getTimeZone() + { + return calendar.getTimeZone(); + } + + /** + * Specify whether or not date/time parsing is to be lenient. With + * lenient parsing, the parser may use heuristics to interpret inputs that + * do not precisely match this object's format. With strict parsing, + * inputs must match this object's format. + * + *

This method is equivalent to the following call. + *

+     *  getCalendar().setLenient(lenient)
+     * 
+ * + *

This leniency value is overwritten by a call to {@link + * #setCalendar(java.util.Calendar) setCalendar()}. + * + * @param lenient when {@code true}, parsing is lenient + * @see java.util.Calendar#setLenient(boolean) + */ + public void setLenient(boolean lenient) + { + calendar.setLenient(lenient); + } + + /** + * Tell whether date/time parsing is to be lenient. + * This method is equivalent to the following call. + *

+     *  getCalendar().isLenient()
+     * 
+ * + * @return {@code true} if the {@link #calendar} is lenient; + * {@code false} otherwise. + * @see java.util.Calendar#isLenient() + */ + public boolean isLenient() + { + return calendar.isLenient(); + } + + /** + * Overrides hashCode + */ + public int hashCode() { + return numberFormat.hashCode(); + // just enough fields for a reasonable distribution + } + + /** + * Overrides equals + */ + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + DateFormat other = (DateFormat) obj; + return (// calendar.equivalentTo(other.calendar) // THIS API DOESN'T EXIST YET! + calendar.getFirstDayOfWeek() == other.calendar.getFirstDayOfWeek() && + calendar.getMinimalDaysInFirstWeek() == other.calendar.getMinimalDaysInFirstWeek() && + calendar.isLenient() == other.calendar.isLenient() && + calendar.getTimeZone().equals(other.calendar.getTimeZone()) && + numberFormat.equals(other.numberFormat)); + } + + /** + * Overrides Cloneable + */ + public Object clone() + { + DateFormat other = (DateFormat) super.clone(); + other.calendar = (Calendar) calendar.clone(); + other.numberFormat = (NumberFormat) numberFormat.clone(); + return other; + } + + /** + * Creates a DateFormat with the given time and/or date style in the given + * locale. + * @param timeStyle a value from 0 to 3 indicating the time format, + * ignored if flags is 2 + * @param dateStyle a value from 0 to 3 indicating the time format, + * ignored if flags is 1 + * @param flags either 1 for a time format, 2 for a date format, + * or 3 for a date/time format + * @param loc the locale for the format + */ + private static DateFormat get(int timeStyle, int dateStyle, + int flags, Locale loc) { + if ((flags & 1) != 0) { + if (timeStyle < 0 || timeStyle > 3) { + throw new IllegalArgumentException("Illegal time style " + timeStyle); + } + } else { + timeStyle = -1; + } + if ((flags & 2) != 0) { + if (dateStyle < 0 || dateStyle > 3) { + throw new IllegalArgumentException("Illegal date style " + dateStyle); + } + } else { + dateStyle = -1; + } + try { + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(DateFormatProvider.class); + if (pool.hasProviders()) { + DateFormat providersInstance = pool.getLocalizedObject( + DateFormatGetter.INSTANCE, + loc, + timeStyle, + dateStyle, + flags); + if (providersInstance != null) { + return providersInstance; + } + } + + return new SimpleDateFormat(timeStyle, dateStyle, loc); + } catch (MissingResourceException e) { + return new SimpleDateFormat("M/d/yy h:mm a"); + } + } + + /** + * Create a new date format. + */ + protected DateFormat() {} + + /** + * Defines constants that are used as attribute keys in the + * AttributedCharacterIterator returned + * from DateFormat.formatToCharacterIterator and as + * field identifiers in FieldPosition. + *

+ * The class also provides two methods to map + * between its constants and the corresponding Calendar constants. + * + * @since 1.4 + * @see java.util.Calendar + */ + public static class Field extends Format.Field { + + // Proclaim serial compatibility with 1.4 FCS + private static final long serialVersionUID = 7441350119349544720L; + + // table of all instances in this class, used by readResolve + private static final Map instanceMap = new HashMap(18); + // Maps from Calendar constant (such as Calendar.ERA) to Field + // constant (such as Field.ERA). + private static final Field[] calendarToFieldMapping = + new Field[Calendar.FIELD_COUNT]; + + /** Calendar field. */ + private int calendarField; + + /** + * Returns the Field constant that corresponds to + * the Calendar constant calendarField. + * If there is no direct mapping between the Calendar + * constant and a Field, null is returned. + * + * @throws IllegalArgumentException if calendarField is + * not the value of a Calendar field constant. + * @param calendarField Calendar field constant + * @return Field instance representing calendarField. + * @see java.util.Calendar + */ + public static Field ofCalendarField(int calendarField) { + if (calendarField < 0 || calendarField >= + calendarToFieldMapping.length) { + throw new IllegalArgumentException("Unknown Calendar constant " + + calendarField); + } + return calendarToFieldMapping[calendarField]; + } + + /** + * Creates a Field. + * + * @param name the name of the Field + * @param calendarField the Calendar constant this + * Field corresponds to; any value, even one + * outside the range of legal Calendar values may + * be used, but -1 should be used for values + * that don't correspond to legal Calendar values + */ + protected Field(String name, int calendarField) { + super(name); + this.calendarField = calendarField; + if (this.getClass() == DateFormat.Field.class) { + instanceMap.put(name, this); + if (calendarField >= 0) { + // assert(calendarField < Calendar.FIELD_COUNT); + calendarToFieldMapping[calendarField] = this; + } + } + } + + /** + * Returns the Calendar field associated with this + * attribute. For example, if this represents the hours field of + * a Calendar, this would return + * Calendar.HOUR. If there is no corresponding + * Calendar constant, this will return -1. + * + * @return Calendar constant for this field + * @see java.util.Calendar + */ + public int getCalendarField() { + return calendarField; + } + + /** + * Resolves instances being deserialized to the predefined constants. + * + * @throws InvalidObjectException if the constant could not be + * resolved. + * @return resolved DateFormat.Field constant + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != DateFormat.Field.class) { + throw new InvalidObjectException("subclass didn't correctly implement readResolve"); + } + + Object instance = instanceMap.get(getName()); + if (instance != null) { + return instance; + } else { + throw new InvalidObjectException("unknown attribute name"); + } + } + + // + // The constants + // + + /** + * Constant identifying the era field. + */ + public final static Field ERA = new Field("era", Calendar.ERA); + + /** + * Constant identifying the year field. + */ + public final static Field YEAR = new Field("year", Calendar.YEAR); + + /** + * Constant identifying the month field. + */ + public final static Field MONTH = new Field("month", Calendar.MONTH); + + /** + * Constant identifying the day of month field. + */ + public final static Field DAY_OF_MONTH = new + Field("day of month", Calendar.DAY_OF_MONTH); + + /** + * Constant identifying the hour of day field, where the legal values + * are 1 to 24. + */ + public final static Field HOUR_OF_DAY1 = new Field("hour of day 1",-1); + + /** + * Constant identifying the hour of day field, where the legal values + * are 0 to 23. + */ + public final static Field HOUR_OF_DAY0 = new + Field("hour of day", Calendar.HOUR_OF_DAY); + + /** + * Constant identifying the minute field. + */ + public final static Field MINUTE =new Field("minute", Calendar.MINUTE); + + /** + * Constant identifying the second field. + */ + public final static Field SECOND =new Field("second", Calendar.SECOND); + + /** + * Constant identifying the millisecond field. + */ + public final static Field MILLISECOND = new + Field("millisecond", Calendar.MILLISECOND); + + /** + * Constant identifying the day of week field. + */ + public final static Field DAY_OF_WEEK = new + Field("day of week", Calendar.DAY_OF_WEEK); + + /** + * Constant identifying the day of year field. + */ + public final static Field DAY_OF_YEAR = new + Field("day of year", Calendar.DAY_OF_YEAR); + + /** + * Constant identifying the day of week field. + */ + public final static Field DAY_OF_WEEK_IN_MONTH = + new Field("day of week in month", + Calendar.DAY_OF_WEEK_IN_MONTH); + + /** + * Constant identifying the week of year field. + */ + public final static Field WEEK_OF_YEAR = new + Field("week of year", Calendar.WEEK_OF_YEAR); + + /** + * Constant identifying the week of month field. + */ + public final static Field WEEK_OF_MONTH = new + Field("week of month", Calendar.WEEK_OF_MONTH); + + /** + * Constant identifying the time of day indicator + * (e.g. "a.m." or "p.m.") field. + */ + public final static Field AM_PM = new + Field("am pm", Calendar.AM_PM); + + /** + * Constant identifying the hour field, where the legal values are + * 1 to 12. + */ + public final static Field HOUR1 = new Field("hour 1", -1); + + /** + * Constant identifying the hour field, where the legal values are + * 0 to 11. + */ + public final static Field HOUR0 = new + Field("hour", Calendar.HOUR); + + /** + * Constant identifying the time zone field. + */ + public final static Field TIME_ZONE = new Field("time zone", -1); + } + + /** + * Obtains a DateFormat instance from a DateFormatProvider + * implementation. + */ + private static class DateFormatGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final DateFormatGetter INSTANCE = new DateFormatGetter(); + + public DateFormat getObject(DateFormatProvider dateFormatProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 3; + + int timeStyle = (Integer)params[0]; + int dateStyle = (Integer)params[1]; + int flags = (Integer)params[2]; + + switch (flags) { + case 1: + return dateFormatProvider.getTimeInstance(timeStyle, locale); + case 2: + return dateFormatProvider.getDateInstance(dateStyle, locale); + case 3: + return dateFormatProvider.getDateTimeInstance(dateStyle, timeStyle, locale); + default: + assert false : "should not happen"; + } + + return null; + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/DateFormatSymbols.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DateFormatSymbols.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,794 @@ +/* + * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.text.spi.DateFormatSymbolsProvider; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.spi.LocaleServiceProvider; +import sun.util.LocaleServiceProviderPool; +import sun.util.TimeZoneNameUtility; +import sun.util.calendar.ZoneInfo; +import sun.util.resources.LocaleData; + +/** + * DateFormatSymbols is a public class for encapsulating + * localizable date-time formatting data, such as the names of the + * months, the names of the days of the week, and the time zone data. + * DateFormat and SimpleDateFormat both use + * DateFormatSymbols to encapsulate this information. + * + *

+ * Typically you shouldn't use DateFormatSymbols directly. + * Rather, you are encouraged to create a date-time formatter with the + * DateFormat class's factory methods: getTimeInstance, + * getDateInstance, or getDateTimeInstance. + * These methods automatically create a DateFormatSymbols for + * the formatter so that you don't have to. After the + * formatter is created, you may modify its format pattern using the + * setPattern method. For more information about + * creating formatters using DateFormat's factory methods, + * see {@link DateFormat}. + * + *

+ * If you decide to create a date-time formatter with a specific + * format pattern for a specific locale, you can do so with: + *

+ *
+ * new SimpleDateFormat(aPattern, DateFormatSymbols.getInstance(aLocale)).
+ * 
+ *
+ * + *

+ * DateFormatSymbols objects are cloneable. When you obtain + * a DateFormatSymbols object, feel free to modify the + * date-time formatting data. For instance, you can replace the localized + * date-time format pattern characters with the ones that you feel easy + * to remember. Or you can change the representative cities + * to your favorite ones. + * + *

+ * New DateFormatSymbols subclasses may be added to support + * SimpleDateFormat for date-time formatting for additional locales. + + * @see DateFormat + * @see SimpleDateFormat + * @see java.util.SimpleTimeZone + * @author Chen-Lieh Huang + */ +public class DateFormatSymbols implements Serializable, Cloneable { + + /** + * Construct a DateFormatSymbols object by loading format data from + * resources for the default locale. This constructor can only + * construct instances for the locales supported by the Java + * runtime environment, not for those supported by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. For full locale coverage, use the + * {@link #getInstance(Locale) getInstance} method. + * + * @see #getInstance() + * @exception java.util.MissingResourceException + * if the resources for the default locale cannot be + * found or cannot be loaded. + */ + public DateFormatSymbols() + { + initializeData(Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Construct a DateFormatSymbols object by loading format data from + * resources for the given locale. This constructor can only + * construct instances for the locales supported by the Java + * runtime environment, not for those supported by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. For full locale coverage, use the + * {@link #getInstance(Locale) getInstance} method. + * + * @see #getInstance(Locale) + * @exception java.util.MissingResourceException + * if the resources for the specified locale cannot be + * found or cannot be loaded. + */ + public DateFormatSymbols(Locale locale) + { + initializeData(locale); + } + + /** + * Era strings. For example: "AD" and "BC". An array of 2 strings, + * indexed by Calendar.BC and Calendar.AD. + * @serial + */ + String eras[] = null; + + /** + * Month strings. For example: "January", "February", etc. An array + * of 13 strings (some calendars have 13 months), indexed by + * Calendar.JANUARY, Calendar.FEBRUARY, etc. + * @serial + */ + String months[] = null; + + /** + * Short month strings. For example: "Jan", "Feb", etc. An array of + * 13 strings (some calendars have 13 months), indexed by + * Calendar.JANUARY, Calendar.FEBRUARY, etc. + + * @serial + */ + String shortMonths[] = null; + + /** + * Weekday strings. For example: "Sunday", "Monday", etc. An array + * of 8 strings, indexed by Calendar.SUNDAY, + * Calendar.MONDAY, etc. + * The element weekdays[0] is ignored. + * @serial + */ + String weekdays[] = null; + + /** + * Short weekday strings. For example: "Sun", "Mon", etc. An array + * of 8 strings, indexed by Calendar.SUNDAY, + * Calendar.MONDAY, etc. + * The element shortWeekdays[0] is ignored. + * @serial + */ + String shortWeekdays[] = null; + + /** + * AM and PM strings. For example: "AM" and "PM". An array of + * 2 strings, indexed by Calendar.AM and + * Calendar.PM. + * @serial + */ + String ampms[] = null; + + /** + * Localized names of time zones in this locale. This is a + * two-dimensional array of strings of size n by m, + * where m is at least 5. Each of the n rows is an + * entry containing the localized names for a single TimeZone. + * Each such row contains (with i ranging from + * 0..n-1): + *

    + *
  • zoneStrings[i][0] - time zone ID
  • + *
  • zoneStrings[i][1] - long name of zone in standard + * time
  • + *
  • zoneStrings[i][2] - short name of zone in + * standard time
  • + *
  • zoneStrings[i][3] - long name of zone in daylight + * saving time
  • + *
  • zoneStrings[i][4] - short name of zone in daylight + * saving time
  • + *
+ * The zone ID is not localized; it's one of the valid IDs of + * the {@link java.util.TimeZone TimeZone} class that are not + * custom IDs. + * All other entries are localized names. + * @see java.util.TimeZone + * @serial + */ + String zoneStrings[][] = null; + + /** + * Indicates that zoneStrings is set externally with setZoneStrings() method. + */ + transient boolean isZoneStringsSet = false; + + /** + * Unlocalized date-time pattern characters. For example: 'y', 'd', etc. + * All locales use the same these unlocalized pattern characters. + */ + static final String patternChars = "GyMdkHmsSEDFwWahKzZYuX"; + + static final int PATTERN_ERA = 0; // G + static final int PATTERN_YEAR = 1; // y + static final int PATTERN_MONTH = 2; // M + static final int PATTERN_DAY_OF_MONTH = 3; // d + static final int PATTERN_HOUR_OF_DAY1 = 4; // k + static final int PATTERN_HOUR_OF_DAY0 = 5; // H + static final int PATTERN_MINUTE = 6; // m + static final int PATTERN_SECOND = 7; // s + static final int PATTERN_MILLISECOND = 8; // S + static final int PATTERN_DAY_OF_WEEK = 9; // E + static final int PATTERN_DAY_OF_YEAR = 10; // D + static final int PATTERN_DAY_OF_WEEK_IN_MONTH = 11; // F + static final int PATTERN_WEEK_OF_YEAR = 12; // w + static final int PATTERN_WEEK_OF_MONTH = 13; // W + static final int PATTERN_AM_PM = 14; // a + static final int PATTERN_HOUR1 = 15; // h + static final int PATTERN_HOUR0 = 16; // K + static final int PATTERN_ZONE_NAME = 17; // z + static final int PATTERN_ZONE_VALUE = 18; // Z + static final int PATTERN_WEEK_YEAR = 19; // Y + static final int PATTERN_ISO_DAY_OF_WEEK = 20; // u + static final int PATTERN_ISO_ZONE = 21; // X + + /** + * Localized date-time pattern characters. For example, a locale may + * wish to use 'u' rather than 'y' to represent years in its date format + * pattern strings. + * This string must be exactly 18 characters long, with the index of + * the characters described by DateFormat.ERA_FIELD, + * DateFormat.YEAR_FIELD, etc. Thus, if the string were + * "Xz...", then localized patterns would use 'X' for era and 'z' for year. + * @serial + */ + String localPatternChars = null; + + /** + * The locale which is used for initializing this DateFormatSymbols object. + * + * @since 1.6 + * @serial + */ + Locale locale = null; + + /* use serialVersionUID from JDK 1.1.4 for interoperability */ + static final long serialVersionUID = -5987973545549424702L; + + /** + * Returns an array of all locales for which the + * getInstance methods of this class can return + * localized instances. + * The returned array represents the union of locales supported by the + * Java runtime and by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. It must contain at least a Locale + * instance equal to {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * DateFormatSymbols instances are available. + * @since 1.6 + */ + public static Locale[] getAvailableLocales() { + LocaleServiceProviderPool pool= + LocaleServiceProviderPool.getPool(DateFormatSymbolsProvider.class); + return pool.getAvailableLocales(); + } + + /** + * Gets the DateFormatSymbols instance for the default + * locale. This method provides access to DateFormatSymbols + * instances for locales supported by the Java runtime itself as well + * as for those supported by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. + * @return a DateFormatSymbols instance. + * @since 1.6 + */ + public static final DateFormatSymbols getInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the DateFormatSymbols instance for the specified + * locale. This method provides access to DateFormatSymbols + * instances for locales supported by the Java runtime itself as well + * as for those supported by installed + * {@link java.text.spi.DateFormatSymbolsProvider DateFormatSymbolsProvider} + * implementations. + * @param locale the given locale. + * @return a DateFormatSymbols instance. + * @exception NullPointerException if locale is null + * @since 1.6 + */ + public static final DateFormatSymbols getInstance(Locale locale) { + DateFormatSymbols dfs = getProviderInstance(locale); + if (dfs != null) { + return dfs; + } + return (DateFormatSymbols) getCachedInstance(locale).clone(); + } + + /** + * Returns a DateFormatSymbols provided by a provider or found in + * the cache. Note that this method returns a cached instance, + * not its clone. Therefore, the instance should never be given to + * an application. + */ + static final DateFormatSymbols getInstanceRef(Locale locale) { + DateFormatSymbols dfs = getProviderInstance(locale); + if (dfs != null) { + return dfs; + } + return getCachedInstance(locale); + } + + private static DateFormatSymbols getProviderInstance(Locale locale) { + DateFormatSymbols providersInstance = null; + + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(DateFormatSymbolsProvider.class); + if (pool.hasProviders()) { + providersInstance = pool.getLocalizedObject( + DateFormatSymbolsGetter.INSTANCE, locale); + } + return providersInstance; + } + + /** + * Returns a cached DateFormatSymbols if it's found in the + * cache. Otherwise, this method returns a newly cached instance + * for the given locale. + */ + private static DateFormatSymbols getCachedInstance(Locale locale) { + SoftReference ref = cachedInstances.get(locale); + DateFormatSymbols dfs = null; + if (ref == null || (dfs = ref.get()) == null) { + dfs = new DateFormatSymbols(locale); + ref = new SoftReference(dfs); + SoftReference x = cachedInstances.putIfAbsent(locale, ref); + if (x != null) { + DateFormatSymbols y = x.get(); + if (y != null) { + dfs = y; + } else { + // Replace the empty SoftReference with ref. + cachedInstances.put(locale, ref); + } + } + } + return dfs; + } + + /** + * Gets era strings. For example: "AD" and "BC". + * @return the era strings. + */ + public String[] getEras() { + return Arrays.copyOf(eras, eras.length); + } + + /** + * Sets era strings. For example: "AD" and "BC". + * @param newEras the new era strings. + */ + public void setEras(String[] newEras) { + eras = Arrays.copyOf(newEras, newEras.length); + } + + /** + * Gets month strings. For example: "January", "February", etc. + * @return the month strings. + */ + public String[] getMonths() { + return Arrays.copyOf(months, months.length); + } + + /** + * Sets month strings. For example: "January", "February", etc. + * @param newMonths the new month strings. + */ + public void setMonths(String[] newMonths) { + months = Arrays.copyOf(newMonths, newMonths.length); + } + + /** + * Gets short month strings. For example: "Jan", "Feb", etc. + * @return the short month strings. + */ + public String[] getShortMonths() { + return Arrays.copyOf(shortMonths, shortMonths.length); + } + + /** + * Sets short month strings. For example: "Jan", "Feb", etc. + * @param newShortMonths the new short month strings. + */ + public void setShortMonths(String[] newShortMonths) { + shortMonths = Arrays.copyOf(newShortMonths, newShortMonths.length); + } + + /** + * Gets weekday strings. For example: "Sunday", "Monday", etc. + * @return the weekday strings. Use Calendar.SUNDAY, + * Calendar.MONDAY, etc. to index the result array. + */ + public String[] getWeekdays() { + return Arrays.copyOf(weekdays, weekdays.length); + } + + /** + * Sets weekday strings. For example: "Sunday", "Monday", etc. + * @param newWeekdays the new weekday strings. The array should + * be indexed by Calendar.SUNDAY, + * Calendar.MONDAY, etc. + */ + public void setWeekdays(String[] newWeekdays) { + weekdays = Arrays.copyOf(newWeekdays, newWeekdays.length); + } + + /** + * Gets short weekday strings. For example: "Sun", "Mon", etc. + * @return the short weekday strings. Use Calendar.SUNDAY, + * Calendar.MONDAY, etc. to index the result array. + */ + public String[] getShortWeekdays() { + return Arrays.copyOf(shortWeekdays, shortWeekdays.length); + } + + /** + * Sets short weekday strings. For example: "Sun", "Mon", etc. + * @param newShortWeekdays the new short weekday strings. The array should + * be indexed by Calendar.SUNDAY, + * Calendar.MONDAY, etc. + */ + public void setShortWeekdays(String[] newShortWeekdays) { + shortWeekdays = Arrays.copyOf(newShortWeekdays, newShortWeekdays.length); + } + + /** + * Gets ampm strings. For example: "AM" and "PM". + * @return the ampm strings. + */ + public String[] getAmPmStrings() { + return Arrays.copyOf(ampms, ampms.length); + } + + /** + * Sets ampm strings. For example: "AM" and "PM". + * @param newAmpms the new ampm strings. + */ + public void setAmPmStrings(String[] newAmpms) { + ampms = Arrays.copyOf(newAmpms, newAmpms.length); + } + + /** + * Gets time zone strings. Use of this method is discouraged; use + * {@link java.util.TimeZone#getDisplayName() TimeZone.getDisplayName()} + * instead. + *

+ * The value returned is a + * two-dimensional array of strings of size n by m, + * where m is at least 5. Each of the n rows is an + * entry containing the localized names for a single TimeZone. + * Each such row contains (with i ranging from + * 0..n-1): + *

    + *
  • zoneStrings[i][0] - time zone ID
  • + *
  • zoneStrings[i][1] - long name of zone in standard + * time
  • + *
  • zoneStrings[i][2] - short name of zone in + * standard time
  • + *
  • zoneStrings[i][3] - long name of zone in daylight + * saving time
  • + *
  • zoneStrings[i][4] - short name of zone in daylight + * saving time
  • + *
+ * The zone ID is not localized; it's one of the valid IDs of + * the {@link java.util.TimeZone TimeZone} class that are not + * custom IDs. + * All other entries are localized names. If a zone does not implement + * daylight saving time, the daylight saving time names should not be used. + *

+ * If {@link #setZoneStrings(String[][]) setZoneStrings} has been called + * on this DateFormatSymbols instance, then the strings + * provided by that call are returned. Otherwise, the returned array + * contains names provided by the Java runtime and by installed + * {@link java.util.spi.TimeZoneNameProvider TimeZoneNameProvider} + * implementations. + * + * @return the time zone strings. + * @see #setZoneStrings(String[][]) + */ + public String[][] getZoneStrings() { + return getZoneStringsImpl(true); + } + + /** + * Sets time zone strings. The argument must be a + * two-dimensional array of strings of size n by m, + * where m is at least 5. Each of the n rows is an + * entry containing the localized names for a single TimeZone. + * Each such row contains (with i ranging from + * 0..n-1): + *

    + *
  • zoneStrings[i][0] - time zone ID
  • + *
  • zoneStrings[i][1] - long name of zone in standard + * time
  • + *
  • zoneStrings[i][2] - short name of zone in + * standard time
  • + *
  • zoneStrings[i][3] - long name of zone in daylight + * saving time
  • + *
  • zoneStrings[i][4] - short name of zone in daylight + * saving time
  • + *
+ * The zone ID is not localized; it's one of the valid IDs of + * the {@link java.util.TimeZone TimeZone} class that are not + * custom IDs. + * All other entries are localized names. + * + * @param newZoneStrings the new time zone strings. + * @exception IllegalArgumentException if the length of any row in + * newZoneStrings is less than 5 + * @exception NullPointerException if newZoneStrings is null + * @see #getZoneStrings() + */ + public void setZoneStrings(String[][] newZoneStrings) { + String[][] aCopy = new String[newZoneStrings.length][]; + for (int i = 0; i < newZoneStrings.length; ++i) { + int len = newZoneStrings[i].length; + if (len < 5) { + throw new IllegalArgumentException(); + } + aCopy[i] = Arrays.copyOf(newZoneStrings[i], len); + } + zoneStrings = aCopy; + isZoneStringsSet = true; + } + + /** + * Gets localized date-time pattern characters. For example: 'u', 't', etc. + * @return the localized date-time pattern characters. + */ + public String getLocalPatternChars() { + return localPatternChars; + } + + /** + * Sets localized date-time pattern characters. For example: 'u', 't', etc. + * @param newLocalPatternChars the new localized date-time + * pattern characters. + */ + public void setLocalPatternChars(String newLocalPatternChars) { + // Call toString() to throw an NPE in case the argument is null + localPatternChars = newLocalPatternChars.toString(); + } + + /** + * Overrides Cloneable + */ + public Object clone() + { + try + { + DateFormatSymbols other = (DateFormatSymbols)super.clone(); + copyMembers(this, other); + return other; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Override hashCode. + * Generates a hash code for the DateFormatSymbols object. + */ + public int hashCode() { + int hashcode = 0; + String[][] zoneStrings = getZoneStringsWrapper(); + for (int index = 0; index < zoneStrings[0].length; ++index) + hashcode ^= zoneStrings[0][index].hashCode(); + return hashcode; + } + + /** + * Override equals + */ + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + DateFormatSymbols that = (DateFormatSymbols) obj; + return (Arrays.equals(eras, that.eras) + && Arrays.equals(months, that.months) + && Arrays.equals(shortMonths, that.shortMonths) + && Arrays.equals(weekdays, that.weekdays) + && Arrays.equals(shortWeekdays, that.shortWeekdays) + && Arrays.equals(ampms, that.ampms) + && Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper()) + && ((localPatternChars != null + && localPatternChars.equals(that.localPatternChars)) + || (localPatternChars == null + && that.localPatternChars == null))); + } + + // =======================privates=============================== + + /** + * Useful constant for defining time zone offsets. + */ + static final int millisPerHour = 60*60*1000; + + /** + * Cache to hold DateFormatSymbols instances per Locale. + */ + private static final ConcurrentMap> cachedInstances + = new ConcurrentHashMap>(3); + + private void initializeData(Locale desiredLocale) { + locale = desiredLocale; + + // Copy values of a cached instance if any. + SoftReference ref = cachedInstances.get(locale); + DateFormatSymbols dfs; + if (ref != null && (dfs = ref.get()) != null) { + copyMembers(dfs, this); + return; + } + + // Initialize the fields from the ResourceBundle for locale. + ResourceBundle resource = LocaleData.getDateFormatData(locale); + + eras = resource.getStringArray("Eras"); + months = resource.getStringArray("MonthNames"); + shortMonths = resource.getStringArray("MonthAbbreviations"); + ampms = resource.getStringArray("AmPmMarkers"); + localPatternChars = resource.getString("DateTimePatternChars"); + + // Day of week names are stored in a 1-based array. + weekdays = toOneBasedArray(resource.getStringArray("DayNames")); + shortWeekdays = toOneBasedArray(resource.getStringArray("DayAbbreviations")); + } + + private static String[] toOneBasedArray(String[] src) { + int len = src.length; + String[] dst = new String[len + 1]; + dst[0] = ""; + for (int i = 0; i < len; i++) { + dst[i + 1] = src[i]; + } + return dst; + } + + /** + * Package private: used by SimpleDateFormat + * Gets the index for the given time zone ID to obtain the time zone + * strings for formatting. The time zone ID is just for programmatic + * lookup. NOT LOCALIZED!!! + * @param ID the given time zone ID. + * @return the index of the given time zone ID. Returns -1 if + * the given time zone ID can't be located in the DateFormatSymbols object. + * @see java.util.SimpleTimeZone + */ + final int getZoneIndex(String ID) + { + String[][] zoneStrings = getZoneStringsWrapper(); + for (int index=0; indexzoneStrings field is initialized in order to make + * sure the backward compatibility. + * + * @since 1.6 + */ + private void writeObject(ObjectOutputStream stream) throws IOException { + if (zoneStrings == null) { + zoneStrings = TimeZoneNameUtility.getZoneStrings(locale); + } + stream.defaultWriteObject(); + } + + /** + * Obtains a DateFormatSymbols instance from a DateFormatSymbolsProvider + * implementation. + */ + private static class DateFormatSymbolsGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final DateFormatSymbolsGetter INSTANCE = + new DateFormatSymbolsGetter(); + + public DateFormatSymbols getObject(DateFormatSymbolsProvider dateFormatSymbolsProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 0; + return dateFormatSymbolsProvider.getInstance(locale); + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/DecimalFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DecimalFormat.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,3278 @@ +/* + * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Currency; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import sun.util.resources.LocaleData; + +/** + * DecimalFormat is a concrete subclass of + * NumberFormat that formats decimal numbers. It has a variety of + * features designed to make it possible to parse and format numbers in any + * locale, including support for Western, Arabic, and Indic digits. It also + * supports different kinds of numbers, including integers (123), fixed-point + * numbers (123.4), scientific notation (1.23E4), percentages (12%), and + * currency amounts ($123). All of these can be localized. + * + *

To obtain a NumberFormat for a specific locale, including the + * default locale, call one of NumberFormat's factory methods, such + * as getInstance(). In general, do not call the + * DecimalFormat constructors directly, since the + * NumberFormat factory methods may return subclasses other than + * DecimalFormat. If you need to customize the format object, do + * something like this: + * + *

+ * NumberFormat f = NumberFormat.getInstance(loc);
+ * if (f instanceof DecimalFormat) {
+ *     ((DecimalFormat) f).setDecimalSeparatorAlwaysShown(true);
+ * }
+ * 
+ * + *

A DecimalFormat comprises a pattern and a set of + * symbols. The pattern may be set directly using + * applyPattern(), or indirectly using the API methods. The + * symbols are stored in a DecimalFormatSymbols object. When using + * the NumberFormat factory methods, the pattern and symbols are + * read from localized ResourceBundles. + * + *

Patterns

+ * + * DecimalFormat patterns have the following syntax: + *
+ * Pattern:
+ *         PositivePattern
+ *         PositivePattern ; NegativePattern
+ * PositivePattern:
+ *         Prefixopt Number Suffixopt
+ * NegativePattern:
+ *         Prefixopt Number Suffixopt
+ * Prefix:
+ *         any Unicode characters except \uFFFE, \uFFFF, and special characters
+ * Suffix:
+ *         any Unicode characters except \uFFFE, \uFFFF, and special characters
+ * Number:
+ *         Integer Exponentopt
+ *         Integer . Fraction Exponentopt
+ * Integer:
+ *         MinimumInteger
+ *         #
+ *         # Integer
+ *         # , Integer
+ * MinimumInteger:
+ *         0
+ *         0 MinimumInteger
+ *         0 , MinimumInteger
+ * Fraction:
+ *         MinimumFractionopt OptionalFractionopt
+ * MinimumFraction:
+ *         0 MinimumFractionopt
+ * OptionalFraction:
+ *         # OptionalFractionopt
+ * Exponent:
+ *         E MinimumExponent
+ * MinimumExponent:
+ *         0 MinimumExponentopt
+ * 
+ * + *

A DecimalFormat pattern contains a positive and negative + * subpattern, for example, "#,##0.00;(#,##0.00)". Each + * subpattern has a prefix, numeric part, and suffix. The negative subpattern + * is optional; if absent, then the positive subpattern prefixed with the + * localized minus sign ('-' in most locales) is used as the + * negative subpattern. That is, "0.00" alone is equivalent to + * "0.00;-0.00". If there is an explicit negative subpattern, it + * serves only to specify the negative prefix and suffix; the number of digits, + * minimal digits, and other characteristics are all the same as the positive + * pattern. That means that "#,##0.0#;(#)" produces precisely + * the same behavior as "#,##0.0#;(#,##0.0#)". + * + *

The prefixes, suffixes, and various symbols used for infinity, digits, + * thousands separators, decimal separators, etc. may be set to arbitrary + * values, and they will appear properly during formatting. However, care must + * be taken that the symbols and strings do not conflict, or parsing will be + * unreliable. For example, either the positive and negative prefixes or the + * suffixes must be distinct for DecimalFormat.parse() to be able + * to distinguish positive from negative values. (If they are identical, then + * DecimalFormat will behave as if no negative subpattern was + * specified.) Another example is that the decimal separator and thousands + * separator should be distinct characters, or parsing will be impossible. + * + *

The grouping separator is commonly used for thousands, but in some + * countries it separates ten-thousands. The grouping size is a constant number + * of digits between the grouping characters, such as 3 for 100,000,000 or 4 for + * 1,0000,0000. If you supply a pattern with multiple grouping characters, the + * interval between the last one and the end of the integer is the one that is + * used. So "#,##,###,####" == "######,####" == + * "##,####,####". + * + *

Special Pattern Characters

+ * + *

Many characters in a pattern are taken literally; they are matched during + * parsing and output unchanged during formatting. Special characters, on the + * other hand, stand for other characters, strings, or classes of characters. + * They must be quoted, unless noted otherwise, if they are to appear in the + * prefix or suffix as literals. + * + *

The characters listed here are used in non-localized patterns. Localized + * patterns use the corresponding characters taken from this formatter's + * DecimalFormatSymbols object instead, and these characters lose + * their special status. Two exceptions are the currency sign and quote, which + * are not localized. + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
Symbol + * Location + * Localized? + * Meaning + *
0 + * Number + * Yes + * Digit + *
# + * Number + * Yes + * Digit, zero shows as absent + *
. + * Number + * Yes + * Decimal separator or monetary decimal separator + *
- + * Number + * Yes + * Minus sign + *
, + * Number + * Yes + * Grouping separator + *
E + * Number + * Yes + * Separates mantissa and exponent in scientific notation. + * Need not be quoted in prefix or suffix. + *
; + * Subpattern boundary + * Yes + * Separates positive and negative subpatterns + *
% + * Prefix or suffix + * Yes + * Multiply by 100 and show as percentage + *
\u2030 + * Prefix or suffix + * Yes + * Multiply by 1000 and show as per mille value + *
¤ (\u00A4) + * Prefix or suffix + * No + * Currency sign, replaced by currency symbol. If + * doubled, replaced by international currency symbol. + * If present in a pattern, the monetary decimal separator + * is used instead of the decimal separator. + *
' + * Prefix or suffix + * No + * Used to quote special characters in a prefix or suffix, + * for example, "'#'#" formats 123 to + * "#123". To create a single quote + * itself, use two in a row: "# o''clock". + *
+ *
+ * + *

Scientific Notation

+ * + *

Numbers in scientific notation are expressed as the product of a mantissa + * and a power of ten, for example, 1234 can be expressed as 1.234 x 10^3. The + * mantissa is often in the range 1.0 <= x < 10.0, but it need not be. + * DecimalFormat can be instructed to format and parse scientific + * notation only via a pattern; there is currently no factory method + * that creates a scientific notation format. In a pattern, the exponent + * character immediately followed by one or more digit characters indicates + * scientific notation. Example: "0.###E0" formats the number + * 1234 as "1.234E3". + * + *

    + *
  • The number of digit characters after the exponent character gives the + * minimum exponent digit count. There is no maximum. Negative exponents are + * formatted using the localized minus sign, not the prefix and suffix + * from the pattern. This allows patterns such as "0.###E0 m/s". + * + *
  • The minimum and maximum number of integer digits are interpreted + * together: + * + *
      + *
    • If the maximum number of integer digits is greater than their minimum number + * and greater than 1, it forces the exponent to be a multiple of the maximum + * number of integer digits, and the minimum number of integer digits to be + * interpreted as 1. The most common use of this is to generate + * engineering notation, in which the exponent is a multiple of three, + * e.g., "##0.#####E0". Using this pattern, the number 12345 + * formats to "12.345E3", and 123456 formats to + * "123.456E3". + * + *
    • Otherwise, the minimum number of integer digits is achieved by adjusting the + * exponent. Example: 0.00123 formatted with "00.###E0" yields + * "12.3E-4". + *
    + * + *
  • The number of significant digits in the mantissa is the sum of the + * minimum integer and maximum fraction digits, and is + * unaffected by the maximum integer digits. For example, 12345 formatted with + * "##0.##E0" is "12.3E3". To show all digits, set + * the significant digits count to zero. The number of significant digits + * does not affect parsing. + * + *
  • Exponential patterns may not contain grouping separators. + *
+ * + *

Rounding

+ * + * DecimalFormat provides rounding modes defined in + * {@link java.math.RoundingMode} for formatting. By default, it uses + * {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}. + * + *

Digits

+ * + * For formatting, DecimalFormat uses the ten consecutive + * characters starting with the localized zero digit defined in the + * DecimalFormatSymbols object as digits. For parsing, these + * digits as well as all Unicode decimal digits, as defined by + * {@link Character#digit Character.digit}, are recognized. + * + *

Special Values

+ * + *

NaN is formatted as a string, which typically has a single character + * \uFFFD. This string is determined by the + * DecimalFormatSymbols object. This is the only value for which + * the prefixes and suffixes are not used. + * + *

Infinity is formatted as a string, which typically has a single character + * \u221E, with the positive or negative prefixes and suffixes + * applied. The infinity string is determined by the + * DecimalFormatSymbols object. + * + *

Negative zero ("-0") parses to + *

    + *
  • BigDecimal(0) if isParseBigDecimal() is + * true, + *
  • Long(0) if isParseBigDecimal() is false + * and isParseIntegerOnly() is true, + *
  • Double(-0.0) if both isParseBigDecimal() + * and isParseIntegerOnly() are false. + *
+ * + *

Synchronization

+ * + *

+ * Decimal formats are generally not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + *

Example

+ * + *
+ * // Print out a number using the localized number, integer, currency,
+ * // and percent format for each locale
+ * Locale[] locales = NumberFormat.getAvailableLocales();
+ * double myNumber = -1234.56;
+ * NumberFormat form;
+ * for (int j=0; j<4; ++j) {
+ *     System.out.println("FORMAT");
+ *     for (int i = 0; i < locales.length; ++i) {
+ *         if (locales[i].getCountry().length() == 0) {
+ *            continue; // Skip language-only locales
+ *         }
+ *         System.out.print(locales[i].getDisplayName());
+ *         switch (j) {
+ *         case 0:
+ *             form = NumberFormat.getInstance(locales[i]); break;
+ *         case 1:
+ *             form = NumberFormat.getIntegerInstance(locales[i]); break;
+ *         case 2:
+ *             form = NumberFormat.getCurrencyInstance(locales[i]); break;
+ *         default:
+ *             form = NumberFormat.getPercentInstance(locales[i]); break;
+ *         }
+ *         if (form instanceof DecimalFormat) {
+ *             System.out.print(": " + ((DecimalFormat) form).toPattern());
+ *         }
+ *         System.out.print(" -> " + form.format(myNumber));
+ *         try {
+ *             System.out.println(" -> " + form.parse(form.format(myNumber)));
+ *         } catch (ParseException e) {}
+ *     }
+ * }
+ * 
+ * + * @see Java Tutorial + * @see NumberFormat + * @see DecimalFormatSymbols + * @see ParsePosition + * @author Mark Davis + * @author Alan Liu + */ +public class DecimalFormat extends NumberFormat { + + /** + * Creates a DecimalFormat using the default pattern and symbols + * for the default locale. This is a convenient way to obtain a + * DecimalFormat when internationalization is not the main concern. + *

+ * To obtain standard formats for a given locale, use the factory methods + * on NumberFormat such as getNumberInstance. These factories will + * return the most appropriate sub-class of NumberFormat for a given + * locale. + * + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + */ + public DecimalFormat() { + Locale def = Locale.getDefault(Locale.Category.FORMAT); + // try to get the pattern from the cache + String pattern = cachedLocaleData.get(def); + if (pattern == null) { /* cache miss */ + // Get the pattern for the default locale. + ResourceBundle rb = LocaleData.getNumberFormatData(def); + String[] all = rb.getStringArray("NumberPatterns"); + pattern = all[0]; + /* update cache */ + cachedLocaleData.putIfAbsent(def, pattern); + } + + // Always applyPattern after the symbols are set + this.symbols = new DecimalFormatSymbols(def); + applyPattern(pattern, false); + } + + + /** + * Creates a DecimalFormat using the given pattern and the symbols + * for the default locale. This is a convenient way to obtain a + * DecimalFormat when internationalization is not the main concern. + *

+ * To obtain standard formats for a given locale, use the factory methods + * on NumberFormat such as getNumberInstance. These factories will + * return the most appropriate sub-class of NumberFormat for a given + * locale. + * + * @param pattern A non-localized pattern string. + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + */ + public DecimalFormat(String pattern) { + // Always applyPattern after the symbols are set + this.symbols = new DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT)); + applyPattern(pattern, false); + } + + + /** + * Creates a DecimalFormat using the given pattern and symbols. + * Use this constructor when you need to completely customize the + * behavior of the format. + *

+ * To obtain standard formats for a given + * locale, use the factory methods on NumberFormat such as + * getInstance or getCurrencyInstance. If you need only minor adjustments + * to a standard format, you can modify the format returned by + * a NumberFormat factory method. + * + * @param pattern a non-localized pattern string + * @param symbols the set of symbols to be used + * @exception NullPointerException if any of the given arguments is null + * @exception IllegalArgumentException if the given pattern is invalid + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + * @see java.text.DecimalFormatSymbols + */ + public DecimalFormat (String pattern, DecimalFormatSymbols symbols) { + // Always applyPattern after the symbols are set + this.symbols = (DecimalFormatSymbols)symbols.clone(); + applyPattern(pattern, false); + } + + + // Overrides + /** + * Formats a number and appends the resulting text to the given string + * buffer. + * The number can be of any subclass of {@link java.lang.Number}. + *

+ * This implementation uses the maximum precision permitted. + * @param number the number to format + * @param toAppendTo the StringBuffer to which the formatted + * text is to be appended + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return the value passed in as toAppendTo + * @exception IllegalArgumentException if number is + * null or not an instance of Number. + * @exception NullPointerException if toAppendTo or + * pos is null + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + public final StringBuffer format(Object number, + StringBuffer toAppendTo, + FieldPosition pos) { + if (number instanceof Long || number instanceof Integer || + number instanceof Short || number instanceof Byte || + number instanceof AtomicInteger || + number instanceof AtomicLong || + (number instanceof BigInteger && + ((BigInteger)number).bitLength () < 64)) { + return format(((Number)number).longValue(), toAppendTo, pos); + } else if (number instanceof BigDecimal) { + return format((BigDecimal)number, toAppendTo, pos); + } else if (number instanceof BigInteger) { + return format((BigInteger)number, toAppendTo, pos); + } else if (number instanceof Number) { + return format(((Number)number).doubleValue(), toAppendTo, pos); + } else { + throw new IllegalArgumentException("Cannot format given Object as a Number"); + } + } + + /** + * Formats a double to produce a string. + * @param number The double to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + * @see java.text.FieldPosition + */ + public StringBuffer format(double number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Formats a double to produce a string. + * @param number The double to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + */ + private StringBuffer format(double number, StringBuffer result, + FieldDelegate delegate) { + if (Double.isNaN(number) || + (Double.isInfinite(number) && multiplier == 0)) { + int iFieldStart = result.length(); + result.append(symbols.getNaN()); + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + return result; + } + + /* Detecting whether a double is negative is easy with the exception of + * the value -0.0. This is a double which has a zero mantissa (and + * exponent), but a negative sign bit. It is semantically distinct from + * a zero with a positive sign bit, and this distinction is important + * to certain kinds of computations. However, it's a little tricky to + * detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you may + * ask, does it behave distinctly from +0.0? Well, 1/(-0.0) == + * -Infinity. Proper detection of -0.0 is needed to deal with the + * issues raised by bugs 4106658, 4106667, and 4147706. Liu 7/6/98. + */ + boolean isNegative = ((number < 0.0) || (number == 0.0 && 1/number < 0.0)) ^ (multiplier < 0); + + if (multiplier != 1) { + number *= multiplier; + } + + if (Double.isInfinite(number)) { + if (isNegative) { + append(result, negativePrefix, delegate, + getNegativePrefixFieldPositions(), Field.SIGN); + } else { + append(result, positivePrefix, delegate, + getPositivePrefixFieldPositions(), Field.SIGN); + } + + int iFieldStart = result.length(); + result.append(symbols.getInfinity()); + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + + if (isNegative) { + append(result, negativeSuffix, delegate, + getNegativeSuffixFieldPositions(), Field.SIGN); + } else { + append(result, positiveSuffix, delegate, + getPositiveSuffixFieldPositions(), Field.SIGN); + } + + return result; + } + + if (isNegative) { + number = -number; + } + + // at this point we are guaranteed a nonnegative finite number. + assert(number >= 0 && !Double.isInfinite(number)); + + synchronized(digitList) { + int maxIntDigits = super.getMaximumIntegerDigits(); + int minIntDigits = super.getMinimumIntegerDigits(); + int maxFraDigits = super.getMaximumFractionDigits(); + int minFraDigits = super.getMinimumFractionDigits(); + + digitList.set(isNegative, number, useExponentialNotation ? + maxIntDigits + maxFraDigits : maxFraDigits, + !useExponentialNotation); + return subformat(result, delegate, isNegative, false, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Format a long to produce a string. + * @param number The long to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + * @see java.text.FieldPosition + */ + public StringBuffer format(long number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Format a long to produce a string. + * @param number The long to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(long number, StringBuffer result, + FieldDelegate delegate) { + boolean isNegative = (number < 0); + if (isNegative) { + number = -number; + } + + // In general, long values always represent real finite numbers, so + // we don't have to check for +/- Infinity or NaN. However, there + // is one case we have to be careful of: The multiplier can push + // a number near MIN_VALUE or MAX_VALUE outside the legal range. We + // check for this before multiplying, and if it happens we use + // BigInteger instead. + boolean useBigInteger = false; + if (number < 0) { // This can only happen if number == Long.MIN_VALUE. + if (multiplier != 0) { + useBigInteger = true; + } + } else if (multiplier != 1 && multiplier != 0) { + long cutoff = Long.MAX_VALUE / multiplier; + if (cutoff < 0) { + cutoff = -cutoff; + } + useBigInteger = (number > cutoff); + } + + if (useBigInteger) { + if (isNegative) { + number = -number; + } + BigInteger bigIntegerValue = BigInteger.valueOf(number); + return format(bigIntegerValue, result, delegate, true); + } + + number *= multiplier; + if (number == 0) { + isNegative = false; + } else { + if (multiplier < 0) { + number = -number; + isNegative = !isNegative; + } + } + + synchronized(digitList) { + int maxIntDigits = super.getMaximumIntegerDigits(); + int minIntDigits = super.getMinimumIntegerDigits(); + int maxFraDigits = super.getMaximumFractionDigits(); + int minFraDigits = super.getMinimumFractionDigits(); + + digitList.set(isNegative, number, + useExponentialNotation ? maxIntDigits + maxFraDigits : 0); + + return subformat(result, delegate, isNegative, true, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Formats a BigDecimal to produce a string. + * @param number The BigDecimal to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigDecimal number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Formats a BigDecimal to produce a string. + * @param number The BigDecimal to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + */ + private StringBuffer format(BigDecimal number, StringBuffer result, + FieldDelegate delegate) { + if (multiplier != 1) { + number = number.multiply(getBigDecimalMultiplier()); + } + boolean isNegative = number.signum() == -1; + if (isNegative) { + number = number.negate(); + } + + synchronized(digitList) { + int maxIntDigits = getMaximumIntegerDigits(); + int minIntDigits = getMinimumIntegerDigits(); + int maxFraDigits = getMaximumFractionDigits(); + int minFraDigits = getMinimumFractionDigits(); + int maximumDigits = maxIntDigits + maxFraDigits; + + digitList.set(isNegative, number, useExponentialNotation ? + ((maximumDigits < 0) ? Integer.MAX_VALUE : maximumDigits) : + maxFraDigits, !useExponentialNotation); + + return subformat(result, delegate, isNegative, false, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Format a BigInteger to produce a string. + * @param number The BigInteger to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigInteger number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + + return format(number, result, fieldPosition.getFieldDelegate(), false); + } + + /** + * Format a BigInteger to produce a string. + * @param number The BigInteger to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigInteger number, StringBuffer result, + FieldDelegate delegate, boolean formatLong) { + if (multiplier != 1) { + number = number.multiply(getBigIntegerMultiplier()); + } + boolean isNegative = number.signum() == -1; + if (isNegative) { + number = number.negate(); + } + + synchronized(digitList) { + int maxIntDigits, minIntDigits, maxFraDigits, minFraDigits, maximumDigits; + if (formatLong) { + maxIntDigits = super.getMaximumIntegerDigits(); + minIntDigits = super.getMinimumIntegerDigits(); + maxFraDigits = super.getMaximumFractionDigits(); + minFraDigits = super.getMinimumFractionDigits(); + maximumDigits = maxIntDigits + maxFraDigits; + } else { + maxIntDigits = getMaximumIntegerDigits(); + minIntDigits = getMinimumIntegerDigits(); + maxFraDigits = getMaximumFractionDigits(); + minFraDigits = getMinimumFractionDigits(); + maximumDigits = maxIntDigits + maxFraDigits; + if (maximumDigits < 0) { + maximumDigits = Integer.MAX_VALUE; + } + } + + digitList.set(isNegative, number, + useExponentialNotation ? maximumDigits : 0); + + return subformat(result, delegate, isNegative, true, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Formats an Object producing an AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * Each attribute key of the AttributedCharacterIterator will be of type + * NumberFormat.Field, with the attribute value being the + * same as the attribute key. + * + * @exception NullPointerException if obj is null. + * @exception IllegalArgumentException when the Format cannot format the + * given object. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @param obj The object to format + * @return AttributedCharacterIterator describing the formatted value. + * @since 1.4 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object obj) { + CharacterIteratorFieldDelegate delegate = + new CharacterIteratorFieldDelegate(); + StringBuffer sb = new StringBuffer(); + + if (obj instanceof Double || obj instanceof Float) { + format(((Number)obj).doubleValue(), sb, delegate); + } else if (obj instanceof Long || obj instanceof Integer || + obj instanceof Short || obj instanceof Byte || + obj instanceof AtomicInteger || obj instanceof AtomicLong) { + format(((Number)obj).longValue(), sb, delegate); + } else if (obj instanceof BigDecimal) { + format((BigDecimal)obj, sb, delegate); + } else if (obj instanceof BigInteger) { + format((BigInteger)obj, sb, delegate, false); + } else if (obj == null) { + throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + } else { + throw new IllegalArgumentException( + "Cannot format given Object as a Number"); + } + return delegate.getIterator(sb.toString()); + } + + /** + * Complete the formatting of a finite number. On entry, the digitList must + * be filled in with the correct digits. + */ + private StringBuffer subformat(StringBuffer result, FieldDelegate delegate, + boolean isNegative, boolean isInteger, + int maxIntDigits, int minIntDigits, + int maxFraDigits, int minFraDigits) { + // NOTE: This isn't required anymore because DigitList takes care of this. + // + // // The negative of the exponent represents the number of leading + // // zeros between the decimal and the first non-zero digit, for + // // a value < 0.1 (e.g., for 0.00123, -fExponent == 2). If this + // // is more than the maximum fraction digits, then we have an underflow + // // for the printed representation. We recognize this here and set + // // the DigitList representation to zero in this situation. + // + // if (-digitList.decimalAt >= getMaximumFractionDigits()) + // { + // digitList.count = 0; + // } + + char zero = symbols.getZeroDigit(); + int zeroDelta = zero - '0'; // '0' is the DigitList representation of zero + char grouping = symbols.getGroupingSeparator(); + char decimal = isCurrencyFormat ? + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + + /* Per bug 4147706, DecimalFormat must respect the sign of numbers which + * format as zero. This allows sensible computations and preserves + * relations such as signum(1/x) = signum(x), where x is +Infinity or + * -Infinity. Prior to this fix, we always formatted zero values as if + * they were positive. Liu 7/6/98. + */ + if (digitList.isZero()) { + digitList.decimalAt = 0; // Normalize + } + + if (isNegative) { + append(result, negativePrefix, delegate, + getNegativePrefixFieldPositions(), Field.SIGN); + } else { + append(result, positivePrefix, delegate, + getPositivePrefixFieldPositions(), Field.SIGN); + } + + if (useExponentialNotation) { + int iFieldStart = result.length(); + int iFieldEnd = -1; + int fFieldStart = -1; + + // Minimum integer digits are handled in exponential format by + // adjusting the exponent. For example, 0.01234 with 3 minimum + // integer digits is "123.4E-4". + + // Maximum integer digits are interpreted as indicating the + // repeating range. This is useful for engineering notation, in + // which the exponent is restricted to a multiple of 3. For + // example, 0.01234 with 3 maximum integer digits is "12.34e-3". + // If maximum integer digits are > 1 and are larger than + // minimum integer digits, then minimum integer digits are + // ignored. + int exponent = digitList.decimalAt; + int repeat = maxIntDigits; + int minimumIntegerDigits = minIntDigits; + if (repeat > 1 && repeat > minIntDigits) { + // A repeating range is defined; adjust to it as follows. + // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; + // -3,-4,-5=>-6, etc. This takes into account that the + // exponent we have here is off by one from what we expect; + // it is for the format 0.MMMMMx10^n. + if (exponent >= 1) { + exponent = ((exponent - 1) / repeat) * repeat; + } else { + // integer division rounds towards 0 + exponent = ((exponent - repeat) / repeat) * repeat; + } + minimumIntegerDigits = 1; + } else { + // No repeating range is defined; use minimum integer digits. + exponent -= minimumIntegerDigits; + } + + // We now output a minimum number of digits, and more if there + // are more digits, up to the maximum number of digits. We + // place the decimal point after the "integer" digits, which + // are the first (decimalAt - exponent) digits. + int minimumDigits = minIntDigits + minFraDigits; + if (minimumDigits < 0) { // overflow? + minimumDigits = Integer.MAX_VALUE; + } + + // The number of integer digits is handled specially if the number + // is zero, since then there may be no digits. + int integerDigits = digitList.isZero() ? minimumIntegerDigits : + digitList.decimalAt - exponent; + if (minimumDigits < integerDigits) { + minimumDigits = integerDigits; + } + int totalDigits = digitList.count; + if (minimumDigits > totalDigits) { + totalDigits = minimumDigits; + } + boolean addedDecimalSeparator = false; + + for (int i=0; i 0 && count < digitList.decimalAt) { + count = digitList.decimalAt; + } + + // Handle the case where getMaximumIntegerDigits() is smaller + // than the real number of integer digits. If this is so, we + // output the least significant max integer digits. For example, + // the value 1997 printed with 2 max integer digits is just "97". + if (count > maxIntDigits) { + count = maxIntDigits; + digitIndex = digitList.decimalAt - count; + } + + int sizeBeforeIntegerPart = result.length(); + for (int i=count-1; i>=0; --i) { + if (i < digitList.decimalAt && digitIndex < digitList.count) { + // Output a real digit + result.append((char)(digitList.digits[digitIndex++] + zeroDelta)); + } else { + // Output a leading zero + result.append(zero); + } + + // Output grouping separator if necessary. Don't output a + // grouping separator if i==0 though; that's at the end of + // the integer part. + if (isGroupingUsed() && i>0 && (groupingSize != 0) && + (i % groupingSize == 0)) { + int gStart = result.length(); + result.append(grouping); + delegate.formatted(Field.GROUPING_SEPARATOR, + Field.GROUPING_SEPARATOR, gStart, + result.length(), result); + } + } + + // Determine whether or not there are any printable fractional + // digits. If we've used up the digits we know there aren't. + boolean fractionPresent = (minFraDigits > 0) || + (!isInteger && digitIndex < digitList.count); + + // If there is no fraction present, and we haven't printed any + // integer digits, then print a zero. Otherwise we won't print + // _any_ digits, and we won't be able to parse this string. + if (!fractionPresent && result.length() == sizeBeforeIntegerPart) { + result.append(zero); + } + + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + + // Output the decimal separator if we always do so. + int sStart = result.length(); + if (decimalSeparatorAlwaysShown || fractionPresent) { + result.append(decimal); + } + + if (sStart != result.length()) { + delegate.formatted(Field.DECIMAL_SEPARATOR, + Field.DECIMAL_SEPARATOR, + sStart, result.length(), result); + } + int fFieldStart = result.length(); + + for (int i=0; i < maxFraDigits; ++i) { + // Here is where we escape from the loop. We escape if we've + // output the maximum fraction digits (specified in the for + // expression above). + // We also stop when we've output the minimum digits and either: + // we have an integer, so there is no fractional stuff to + // display, or we're out of significant digits. + if (i >= minFraDigits && + (isInteger || digitIndex >= digitList.count)) { + break; + } + + // Output leading fractional zeros. These are zeros that come + // after the decimal but before any significant digits. These + // are only output if abs(number being formatted) < 1.0. + if (-1-i > (digitList.decimalAt-1)) { + result.append(zero); + continue; + } + + // Output a digit, if we have any precision left, or a + // zero if we don't. We don't want to output noise digits. + if (!isInteger && digitIndex < digitList.count) { + result.append((char)(digitList.digits[digitIndex++] + zeroDelta)); + } else { + result.append(zero); + } + } + + // Record field information for caller. + delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION, + fFieldStart, result.length(), result); + } + + if (isNegative) { + append(result, negativeSuffix, delegate, + getNegativeSuffixFieldPositions(), Field.SIGN); + } + else { + append(result, positiveSuffix, delegate, + getPositiveSuffixFieldPositions(), Field.SIGN); + } + + return result; + } + + /** + * Appends the String string to result. + * delegate is notified of all the + * FieldPositions in positions. + *

+ * If one of the FieldPositions in positions + * identifies a SIGN attribute, it is mapped to + * signAttribute. This is used + * to map the SIGN attribute to the EXPONENT + * attribute as necessary. + *

+ * This is used by subformat to add the prefix/suffix. + */ + private void append(StringBuffer result, String string, + FieldDelegate delegate, + FieldPosition[] positions, + Format.Field signAttribute) { + int start = result.length(); + + if (string.length() > 0) { + result.append(string); + for (int counter = 0, max = positions.length; counter < max; + counter++) { + FieldPosition fp = positions[counter]; + Format.Field attribute = fp.getFieldAttribute(); + + if (attribute == Field.SIGN) { + attribute = signAttribute; + } + delegate.formatted(attribute, attribute, + start + fp.getBeginIndex(), + start + fp.getEndIndex(), result); + } + } + } + + /** + * Parses text from a string to produce a Number. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * number is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * The subclass returned depends on the value of {@link #isParseBigDecimal} + * as well as on the string being parsed. + *

    + *
  • If isParseBigDecimal() is false (the default), + * most integer values are returned as Long + * objects, no matter how they are written: "17" and + * "17.000" both parse to Long(17). + * Values that cannot fit into a Long are returned as + * Doubles. This includes values with a fractional part, + * infinite values, NaN, and the value -0.0. + * DecimalFormat does not decide whether to + * return a Double or a Long based on the + * presence of a decimal separator in the source string. Doing so + * would prevent integers that overflow the mantissa of a double, + * such as "-9,223,372,036,854,775,808.00", from being + * parsed accurately. + *

    + * Callers may use the Number methods + * doubleValue, longValue, etc., to obtain + * the type they want. + *

  • If isParseBigDecimal() is true, values are returned + * as BigDecimal objects. The values are the ones + * constructed by {@link java.math.BigDecimal#BigDecimal(String)} + * for corresponding strings in locale-independent format. The + * special cases negative and positive infinity and NaN are returned + * as Double instances holding the values of the + * corresponding Double constants. + *
+ *

+ * DecimalFormat parses all Unicode characters that represent + * decimal digits, as defined by Character.digit(). In + * addition, DecimalFormat also recognizes as digits the ten + * consecutive characters starting with the localized zero digit defined in + * the DecimalFormatSymbols object. + * + * @param text the string to be parsed + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return the parsed value, or null if the parse fails + * @exception NullPointerException if text or + * pos is null. + */ + public Number parse(String text, ParsePosition pos) { + // special case NaN + if (text.regionMatches(pos.index, symbols.getNaN(), 0, symbols.getNaN().length())) { + pos.index = pos.index + symbols.getNaN().length(); + return new Double(Double.NaN); + } + + boolean[] status = new boolean[STATUS_LENGTH]; + if (!subparse(text, pos, positivePrefix, negativePrefix, digitList, false, status)) { + return null; + } + + // special case INFINITY + if (status[STATUS_INFINITE]) { + if (status[STATUS_POSITIVE] == (multiplier >= 0)) { + return new Double(Double.POSITIVE_INFINITY); + } else { + return new Double(Double.NEGATIVE_INFINITY); + } + } + + if (multiplier == 0) { + if (digitList.isZero()) { + return new Double(Double.NaN); + } else if (status[STATUS_POSITIVE]) { + return new Double(Double.POSITIVE_INFINITY); + } else { + return new Double(Double.NEGATIVE_INFINITY); + } + } + + if (isParseBigDecimal()) { + BigDecimal bigDecimalResult = digitList.getBigDecimal(); + + if (multiplier != 1) { + try { + bigDecimalResult = bigDecimalResult.divide(getBigDecimalMultiplier()); + } + catch (ArithmeticException e) { // non-terminating decimal expansion + bigDecimalResult = bigDecimalResult.divide(getBigDecimalMultiplier(), roundingMode); + } + } + + if (!status[STATUS_POSITIVE]) { + bigDecimalResult = bigDecimalResult.negate(); + } + return bigDecimalResult; + } else { + boolean gotDouble = true; + boolean gotLongMinimum = false; + double doubleResult = 0.0; + long longResult = 0; + + // Finally, have DigitList parse the digits into a value. + if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) { + gotDouble = false; + longResult = digitList.getLong(); + if (longResult < 0) { // got Long.MIN_VALUE + gotLongMinimum = true; + } + } else { + doubleResult = digitList.getDouble(); + } + + // Divide by multiplier. We have to be careful here not to do + // unneeded conversions between double and long. + if (multiplier != 1) { + if (gotDouble) { + doubleResult /= multiplier; + } else { + // Avoid converting to double if we can + if (longResult % multiplier == 0) { + longResult /= multiplier; + } else { + doubleResult = ((double)longResult) / multiplier; + gotDouble = true; + } + } + } + + if (!status[STATUS_POSITIVE] && !gotLongMinimum) { + doubleResult = -doubleResult; + longResult = -longResult; + } + + // At this point, if we divided the result by the multiplier, the + // result may fit into a long. We check for this case and return + // a long if possible. + // We must do this AFTER applying the negative (if appropriate) + // in order to handle the case of LONG_MIN; otherwise, if we do + // this with a positive value -LONG_MIN, the double is > 0, but + // the long is < 0. We also must retain a double in the case of + // -0.0, which will compare as == to a long 0 cast to a double + // (bug 4162852). + if (multiplier != 1 && gotDouble) { + longResult = (long)doubleResult; + gotDouble = ((doubleResult != (double)longResult) || + (doubleResult == 0.0 && 1/doubleResult < 0.0)) && + !isParseIntegerOnly(); + } + + return gotDouble ? + (Number)new Double(doubleResult) : (Number)new Long(longResult); + } + } + + /** + * Return a BigInteger multiplier. + */ + private BigInteger getBigIntegerMultiplier() { + if (bigIntegerMultiplier == null) { + bigIntegerMultiplier = BigInteger.valueOf(multiplier); + } + return bigIntegerMultiplier; + } + private transient BigInteger bigIntegerMultiplier; + + /** + * Return a BigDecimal multiplier. + */ + private BigDecimal getBigDecimalMultiplier() { + if (bigDecimalMultiplier == null) { + bigDecimalMultiplier = new BigDecimal(multiplier); + } + return bigDecimalMultiplier; + } + private transient BigDecimal bigDecimalMultiplier; + + private static final int STATUS_INFINITE = 0; + private static final int STATUS_POSITIVE = 1; + private static final int STATUS_LENGTH = 2; + + /** + * Parse the given text into a number. The text is parsed beginning at + * parsePosition, until an unparseable character is seen. + * @param text The string to parse. + * @param parsePosition The position at which to being parsing. Upon + * return, the first unparseable character. + * @param digits The DigitList to set to the parsed value. + * @param isExponent If true, parse an exponent. This means no + * infinite values and integer only. + * @param status Upon return contains boolean status flags indicating + * whether the value was infinite and whether it was positive. + */ + private final boolean subparse(String text, ParsePosition parsePosition, + String positivePrefix, String negativePrefix, + DigitList digits, boolean isExponent, + boolean status[]) { + int position = parsePosition.index; + int oldStart = parsePosition.index; + int backup; + boolean gotPositive, gotNegative; + + // check for positivePrefix; take longest + gotPositive = text.regionMatches(position, positivePrefix, 0, + positivePrefix.length()); + gotNegative = text.regionMatches(position, negativePrefix, 0, + negativePrefix.length()); + + if (gotPositive && gotNegative) { + if (positivePrefix.length() > negativePrefix.length()) { + gotNegative = false; + } else if (positivePrefix.length() < negativePrefix.length()) { + gotPositive = false; + } + } + + if (gotPositive) { + position += positivePrefix.length(); + } else if (gotNegative) { + position += negativePrefix.length(); + } else { + parsePosition.errorIndex = position; + return false; + } + + // process digits or Inf, find decimal position + status[STATUS_INFINITE] = false; + if (!isExponent && text.regionMatches(position,symbols.getInfinity(),0, + symbols.getInfinity().length())) { + position += symbols.getInfinity().length(); + status[STATUS_INFINITE] = true; + } else { + // We now have a string of digits, possibly with grouping symbols, + // and decimal points. We want to process these into a DigitList. + // We don't want to put a bunch of leading zeros into the DigitList + // though, so we keep track of the location of the decimal point, + // put only significant digits into the DigitList, and adjust the + // exponent as needed. + + digits.decimalAt = digits.count = 0; + char zero = symbols.getZeroDigit(); + char decimal = isCurrencyFormat ? + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + char grouping = symbols.getGroupingSeparator(); + String exponentString = symbols.getExponentSeparator(); + boolean sawDecimal = false; + boolean sawExponent = false; + boolean sawDigit = false; + int exponent = 0; // Set to the exponent value, if any + + // We have to track digitCount ourselves, because digits.count will + // pin when the maximum allowable digits is reached. + int digitCount = 0; + + backup = -1; + for (; position < text.length(); ++position) { + char ch = text.charAt(position); + + /* We recognize all digit ranges, not only the Latin digit range + * '0'..'9'. We do so by using the Character.digit() method, + * which converts a valid Unicode digit to the range 0..9. + * + * The character 'ch' may be a digit. If so, place its value + * from 0 to 9 in 'digit'. First try using the locale digit, + * which may or MAY NOT be a standard Unicode digit range. If + * this fails, try using the standard Unicode digit ranges by + * calling Character.digit(). If this also fails, digit will + * have a value outside the range 0..9. + */ + int digit = ch - zero; + if (digit < 0 || digit > 9) { + digit = Character.digit(ch, 10); + } + + if (digit == 0) { + // Cancel out backup setting (see grouping handler below) + backup = -1; // Do this BEFORE continue statement below!!! + sawDigit = true; + + // Handle leading zeros + if (digits.count == 0) { + // Ignore leading zeros in integer part of number. + if (!sawDecimal) { + continue; + } + + // If we have seen the decimal, but no significant + // digits yet, then we account for leading zeros by + // decrementing the digits.decimalAt into negative + // values. + --digits.decimalAt; + } else { + ++digitCount; + digits.append((char)(digit + '0')); + } + } else if (digit > 0 && digit <= 9) { // [sic] digit==0 handled above + sawDigit = true; + ++digitCount; + digits.append((char)(digit + '0')); + + // Cancel out backup setting (see grouping handler below) + backup = -1; + } else if (!isExponent && ch == decimal) { + // If we're only parsing integers, or if we ALREADY saw the + // decimal, then don't parse this one. + if (isParseIntegerOnly() || sawDecimal) { + break; + } + digits.decimalAt = digitCount; // Not digits.count! + sawDecimal = true; + } else if (!isExponent && ch == grouping && isGroupingUsed()) { + if (sawDecimal) { + break; + } + // Ignore grouping characters, if we are using them, but + // require that they be followed by a digit. Otherwise + // we backup and reprocess them. + backup = position; + } else if (!isExponent && text.regionMatches(position, exponentString, 0, exponentString.length()) + && !sawExponent) { + // Process the exponent by recursively calling this method. + ParsePosition pos = new ParsePosition(position + exponentString.length()); + boolean[] stat = new boolean[STATUS_LENGTH]; + DigitList exponentDigits = new DigitList(); + + if (subparse(text, pos, "", Character.toString(symbols.getMinusSign()), exponentDigits, true, stat) && + exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) { + position = pos.index; // Advance past the exponent + exponent = (int)exponentDigits.getLong(); + if (!stat[STATUS_POSITIVE]) { + exponent = -exponent; + } + sawExponent = true; + } + break; // Whether we fail or succeed, we exit this loop + } + else { + break; + } + } + + if (backup != -1) { + position = backup; + } + + // If there was no decimal point we have an integer + if (!sawDecimal) { + digits.decimalAt = digitCount; // Not digits.count! + } + + // Adjust for exponent, if any + digits.decimalAt += exponent; + + // If none of the text string was recognized. For example, parse + // "x" with pattern "#0.00" (return index and error index both 0) + // parse "$" with pattern "$#0.00". (return index 0 and error + // index 1). + if (!sawDigit && digitCount == 0) { + parsePosition.index = oldStart; + parsePosition.errorIndex = oldStart; + return false; + } + } + + // check for suffix + if (!isExponent) { + if (gotPositive) { + gotPositive = text.regionMatches(position,positiveSuffix,0, + positiveSuffix.length()); + } + if (gotNegative) { + gotNegative = text.regionMatches(position,negativeSuffix,0, + negativeSuffix.length()); + } + + // if both match, take longest + if (gotPositive && gotNegative) { + if (positiveSuffix.length() > negativeSuffix.length()) { + gotNegative = false; + } else if (positiveSuffix.length() < negativeSuffix.length()) { + gotPositive = false; + } + } + + // fail if neither or both + if (gotPositive == gotNegative) { + parsePosition.errorIndex = position; + return false; + } + + parsePosition.index = position + + (gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success! + } else { + parsePosition.index = position; + } + + status[STATUS_POSITIVE] = gotPositive; + if (parsePosition.index == oldStart) { + parsePosition.errorIndex = position; + return false; + } + return true; + } + + /** + * Returns a copy of the decimal format symbols, which is generally not + * changed by the programmer or user. + * @return a copy of the desired DecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + public DecimalFormatSymbols getDecimalFormatSymbols() { + try { + // don't allow multiple references + return (DecimalFormatSymbols) symbols.clone(); + } catch (Exception foo) { + return null; // should never happen + } + } + + + /** + * Sets the decimal format symbols, which is generally not changed + * by the programmer or user. + * @param newSymbols desired DecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { + try { + // don't allow multiple references + symbols = (DecimalFormatSymbols) newSymbols.clone(); + expandAffixes(); + } catch (Exception foo) { + // should never happen + } + } + + /** + * Get the positive prefix. + *

Examples: +123, $123, sFr123 + */ + public String getPositivePrefix () { + return positivePrefix; + } + + /** + * Set the positive prefix. + *

Examples: +123, $123, sFr123 + */ + public void setPositivePrefix (String newValue) { + positivePrefix = newValue; + posPrefixPattern = null; + positivePrefixFieldPositions = null; + } + + /** + * Returns the FieldPositions of the fields in the prefix used for + * positive numbers. This is not used if the user has explicitly set + * a positive prefix via setPositivePrefix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getPositivePrefixFieldPositions() { + if (positivePrefixFieldPositions == null) { + if (posPrefixPattern != null) { + positivePrefixFieldPositions = expandAffix(posPrefixPattern); + } + else { + positivePrefixFieldPositions = EmptyFieldPositionArray; + } + } + return positivePrefixFieldPositions; + } + + /** + * Get the negative prefix. + *

Examples: -123, ($123) (with negative suffix), sFr-123 + */ + public String getNegativePrefix () { + return negativePrefix; + } + + /** + * Set the negative prefix. + *

Examples: -123, ($123) (with negative suffix), sFr-123 + */ + public void setNegativePrefix (String newValue) { + negativePrefix = newValue; + negPrefixPattern = null; + } + + /** + * Returns the FieldPositions of the fields in the prefix used for + * negative numbers. This is not used if the user has explicitly set + * a negative prefix via setNegativePrefix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getNegativePrefixFieldPositions() { + if (negativePrefixFieldPositions == null) { + if (negPrefixPattern != null) { + negativePrefixFieldPositions = expandAffix(negPrefixPattern); + } + else { + negativePrefixFieldPositions = EmptyFieldPositionArray; + } + } + return negativePrefixFieldPositions; + } + + /** + * Get the positive suffix. + *

Example: 123% + */ + public String getPositiveSuffix () { + return positiveSuffix; + } + + /** + * Set the positive suffix. + *

Example: 123% + */ + public void setPositiveSuffix (String newValue) { + positiveSuffix = newValue; + posSuffixPattern = null; + } + + /** + * Returns the FieldPositions of the fields in the suffix used for + * positive numbers. This is not used if the user has explicitly set + * a positive suffix via setPositiveSuffix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getPositiveSuffixFieldPositions() { + if (positiveSuffixFieldPositions == null) { + if (posSuffixPattern != null) { + positiveSuffixFieldPositions = expandAffix(posSuffixPattern); + } + else { + positiveSuffixFieldPositions = EmptyFieldPositionArray; + } + } + return positiveSuffixFieldPositions; + } + + /** + * Get the negative suffix. + *

Examples: -123%, ($123) (with positive suffixes) + */ + public String getNegativeSuffix () { + return negativeSuffix; + } + + /** + * Set the negative suffix. + *

Examples: 123% + */ + public void setNegativeSuffix (String newValue) { + negativeSuffix = newValue; + negSuffixPattern = null; + } + + /** + * Returns the FieldPositions of the fields in the suffix used for + * negative numbers. This is not used if the user has explicitly set + * a negative suffix via setNegativeSuffix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getNegativeSuffixFieldPositions() { + if (negativeSuffixFieldPositions == null) { + if (negSuffixPattern != null) { + negativeSuffixFieldPositions = expandAffix(negSuffixPattern); + } + else { + negativeSuffixFieldPositions = EmptyFieldPositionArray; + } + } + return negativeSuffixFieldPositions; + } + + /** + * Gets the multiplier for use in percent, per mille, and similar + * formats. + * + * @see #setMultiplier(int) + */ + public int getMultiplier () { + return multiplier; + } + + /** + * Sets the multiplier for use in percent, per mille, and similar + * formats. + * For a percent format, set the multiplier to 100 and the suffixes to + * have '%' (for Arabic, use the Arabic percent sign). + * For a per mille format, set the multiplier to 1000 and the suffixes to + * have '\u2030'. + * + *

Example: with multiplier 100, 1.23 is formatted as "123", and + * "123" is parsed into 1.23. + * + * @see #getMultiplier + */ + public void setMultiplier (int newValue) { + multiplier = newValue; + bigDecimalMultiplier = null; + bigIntegerMultiplier = null; + } + + /** + * Return the grouping size. Grouping size is the number of digits between + * grouping separators in the integer portion of a number. For example, + * in the number "123,456.78", the grouping size is 3. + * @see #setGroupingSize + * @see java.text.NumberFormat#isGroupingUsed + * @see java.text.DecimalFormatSymbols#getGroupingSeparator + */ + public int getGroupingSize () { + return groupingSize; + } + + /** + * Set the grouping size. Grouping size is the number of digits between + * grouping separators in the integer portion of a number. For example, + * in the number "123,456.78", the grouping size is 3. + *
+ * The value passed in is converted to a byte, which may lose information. + * @see #getGroupingSize + * @see java.text.NumberFormat#setGroupingUsed + * @see java.text.DecimalFormatSymbols#setGroupingSeparator + */ + public void setGroupingSize (int newValue) { + groupingSize = (byte)newValue; + } + + /** + * Allows you to get the behavior of the decimal separator with integers. + * (The decimal separator will always appear with decimals.) + *

Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345 + */ + public boolean isDecimalSeparatorAlwaysShown() { + return decimalSeparatorAlwaysShown; + } + + /** + * Allows you to set the behavior of the decimal separator with integers. + * (The decimal separator will always appear with decimals.) + *

Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345 + */ + public void setDecimalSeparatorAlwaysShown(boolean newValue) { + decimalSeparatorAlwaysShown = newValue; + } + + /** + * Returns whether the {@link #parse(java.lang.String, java.text.ParsePosition)} + * method returns BigDecimal. The default value is false. + * @see #setParseBigDecimal + * @since 1.5 + */ + public boolean isParseBigDecimal() { + return parseBigDecimal; + } + + /** + * Sets whether the {@link #parse(java.lang.String, java.text.ParsePosition)} + * method returns BigDecimal. + * @see #isParseBigDecimal + * @since 1.5 + */ + public void setParseBigDecimal(boolean newValue) { + parseBigDecimal = newValue; + } + + /** + * Standard override; no change in semantics. + */ + public Object clone() { + try { + DecimalFormat other = (DecimalFormat) super.clone(); + other.symbols = (DecimalFormatSymbols) symbols.clone(); + other.digitList = (DigitList) digitList.clone(); + return other; + } catch (Exception e) { + throw new InternalError(); + } + } + + /** + * Overrides equals + */ + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!super.equals(obj)) return false; // super does class check + DecimalFormat other = (DecimalFormat) obj; + return ((posPrefixPattern == other.posPrefixPattern && + positivePrefix.equals(other.positivePrefix)) + || (posPrefixPattern != null && + posPrefixPattern.equals(other.posPrefixPattern))) + && ((posSuffixPattern == other.posSuffixPattern && + positiveSuffix.equals(other.positiveSuffix)) + || (posSuffixPattern != null && + posSuffixPattern.equals(other.posSuffixPattern))) + && ((negPrefixPattern == other.negPrefixPattern && + negativePrefix.equals(other.negativePrefix)) + || (negPrefixPattern != null && + negPrefixPattern.equals(other.negPrefixPattern))) + && ((negSuffixPattern == other.negSuffixPattern && + negativeSuffix.equals(other.negativeSuffix)) + || (negSuffixPattern != null && + negSuffixPattern.equals(other.negSuffixPattern))) + && multiplier == other.multiplier + && groupingSize == other.groupingSize + && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown + && parseBigDecimal == other.parseBigDecimal + && useExponentialNotation == other.useExponentialNotation + && (!useExponentialNotation || + minExponentDigits == other.minExponentDigits) + && maximumIntegerDigits == other.maximumIntegerDigits + && minimumIntegerDigits == other.minimumIntegerDigits + && maximumFractionDigits == other.maximumFractionDigits + && minimumFractionDigits == other.minimumFractionDigits + && roundingMode == other.roundingMode + && symbols.equals(other.symbols); + } + + /** + * Overrides hashCode + */ + public int hashCode() { + return super.hashCode() * 37 + positivePrefix.hashCode(); + // just enough fields for a reasonable distribution + } + + /** + * Synthesizes a pattern string that represents the current state + * of this Format object. + * @see #applyPattern + */ + public String toPattern() { + return toPattern( false ); + } + + /** + * Synthesizes a localized pattern string that represents the current + * state of this Format object. + * @see #applyPattern + */ + public String toLocalizedPattern() { + return toPattern( true ); + } + + /** + * Expand the affix pattern strings into the expanded affix strings. If any + * affix pattern string is null, do not expand it. This method should be + * called any time the symbols or the affix patterns change in order to keep + * the expanded affix strings up to date. + */ + private void expandAffixes() { + // Reuse one StringBuffer for better performance + StringBuffer buffer = new StringBuffer(); + if (posPrefixPattern != null) { + positivePrefix = expandAffix(posPrefixPattern, buffer); + positivePrefixFieldPositions = null; + } + if (posSuffixPattern != null) { + positiveSuffix = expandAffix(posSuffixPattern, buffer); + positiveSuffixFieldPositions = null; + } + if (negPrefixPattern != null) { + negativePrefix = expandAffix(negPrefixPattern, buffer); + negativePrefixFieldPositions = null; + } + if (negSuffixPattern != null) { + negativeSuffix = expandAffix(negSuffixPattern, buffer); + negativeSuffixFieldPositions = null; + } + } + + /** + * Expand an affix pattern into an affix string. All characters in the + * pattern are literal unless prefixed by QUOTE. The following characters + * after QUOTE are recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, + * PATTERN_MINUS, and CURRENCY_SIGN. If CURRENCY_SIGN is doubled (QUOTE + + * CURRENCY_SIGN + CURRENCY_SIGN), it is interpreted as an ISO 4217 + * currency code. Any other character after a QUOTE represents itself. + * QUOTE must be followed by another character; QUOTE may not occur by + * itself at the end of the pattern. + * + * @param pattern the non-null, possibly empty pattern + * @param buffer a scratch StringBuffer; its contents will be lost + * @return the expanded equivalent of pattern + */ + private String expandAffix(String pattern, StringBuffer buffer) { + buffer.setLength(0); + for (int i=0; i 0) { + if (positions == null) { + positions = new ArrayList(2); + } + FieldPosition fp = new FieldPosition(Field.CURRENCY); + fp.setBeginIndex(stringIndex); + fp.setEndIndex(stringIndex + string.length()); + positions.add(fp); + stringIndex += string.length(); + } + continue; + case PATTERN_PERCENT: + c = symbols.getPercent(); + field = -1; + fieldID = Field.PERCENT; + break; + case PATTERN_PER_MILLE: + c = symbols.getPerMill(); + field = -1; + fieldID = Field.PERMILLE; + break; + case PATTERN_MINUS: + c = symbols.getMinusSign(); + field = -1; + fieldID = Field.SIGN; + break; + } + if (fieldID != null) { + if (positions == null) { + positions = new ArrayList(2); + } + FieldPosition fp = new FieldPosition(fieldID, field); + fp.setBeginIndex(stringIndex); + fp.setEndIndex(stringIndex + 1); + positions.add(fp); + } + } + stringIndex++; + } + if (positions != null) { + return (FieldPosition[])positions.toArray(EmptyFieldPositionArray); + } + return EmptyFieldPositionArray; + } + + /** + * Appends an affix pattern to the given StringBuffer, quoting special + * characters as needed. Uses the internal affix pattern, if that exists, + * or the literal affix, if the internal affix pattern is null. The + * appended string will generate the same affix pattern (or literal affix) + * when passed to toPattern(). + * + * @param buffer the affix string is appended to this + * @param affixPattern a pattern such as posPrefixPattern; may be null + * @param expAffix a corresponding expanded affix, such as positivePrefix. + * Ignored unless affixPattern is null. If affixPattern is null, then + * expAffix is appended as a literal affix. + * @param localized true if the appended pattern should contain localized + * pattern characters; otherwise, non-localized pattern chars are appended + */ + private void appendAffix(StringBuffer buffer, String affixPattern, + String expAffix, boolean localized) { + if (affixPattern == null) { + appendAffix(buffer, expAffix, localized); + } else { + int i; + for (int pos=0; pos pos) { + appendAffix(buffer, affixPattern.substring(pos, i), localized); + } + char c = affixPattern.charAt(++i); + ++i; + if (c == QUOTE) { + buffer.append(c); + // Fall through and append another QUOTE below + } else if (c == CURRENCY_SIGN && + i= 0 + || affix.indexOf(symbols.getGroupingSeparator()) >= 0 + || affix.indexOf(symbols.getDecimalSeparator()) >= 0 + || affix.indexOf(symbols.getPercent()) >= 0 + || affix.indexOf(symbols.getPerMill()) >= 0 + || affix.indexOf(symbols.getDigit()) >= 0 + || affix.indexOf(symbols.getPatternSeparator()) >= 0 + || affix.indexOf(symbols.getMinusSign()) >= 0 + || affix.indexOf(CURRENCY_SIGN) >= 0; + } + else { + needQuote = affix.indexOf(PATTERN_ZERO_DIGIT) >= 0 + || affix.indexOf(PATTERN_GROUPING_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_DECIMAL_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_PERCENT) >= 0 + || affix.indexOf(PATTERN_PER_MILLE) >= 0 + || affix.indexOf(PATTERN_DIGIT) >= 0 + || affix.indexOf(PATTERN_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_MINUS) >= 0 + || affix.indexOf(CURRENCY_SIGN) >= 0; + } + if (needQuote) buffer.append('\''); + if (affix.indexOf('\'') < 0) buffer.append(affix); + else { + for (int j=0; j= 0; --j) { + if (j == 1) + appendAffix(result, posPrefixPattern, positivePrefix, localized); + else appendAffix(result, negPrefixPattern, negativePrefix, localized); + int i; + int digitCount = useExponentialNotation + ? getMaximumIntegerDigits() + : Math.max(groupingSize, getMinimumIntegerDigits())+1; + for (i = digitCount; i > 0; --i) { + if (i != digitCount && isGroupingUsed() && groupingSize != 0 && + i % groupingSize == 0) { + result.append(localized ? symbols.getGroupingSeparator() : + PATTERN_GROUPING_SEPARATOR); + } + result.append(i <= getMinimumIntegerDigits() + ? (localized ? symbols.getZeroDigit() : PATTERN_ZERO_DIGIT) + : (localized ? symbols.getDigit() : PATTERN_DIGIT)); + } + if (getMaximumFractionDigits() > 0 || decimalSeparatorAlwaysShown) + result.append(localized ? symbols.getDecimalSeparator() : + PATTERN_DECIMAL_SEPARATOR); + for (i = 0; i < getMaximumFractionDigits(); ++i) { + if (i < getMinimumFractionDigits()) { + result.append(localized ? symbols.getZeroDigit() : + PATTERN_ZERO_DIGIT); + } else { + result.append(localized ? symbols.getDigit() : + PATTERN_DIGIT); + } + } + if (useExponentialNotation) + { + result.append(localized ? symbols.getExponentSeparator() : + PATTERN_EXPONENT); + for (i=0; i + * There is no limit to integer digits set + * by this routine, since that is the typical end-user desire; + * use setMaximumInteger if you want to set a real value. + * For negative numbers, use a second pattern, separated by a semicolon + *

Example "#,#00.0#" -> 1,234.56 + *

This means a minimum of 2 integer digits, 1 fraction digit, and + * a maximum of 2 fraction digits. + *

Example: "#,#00.0#;(#,#00.0#)" for negatives in + * parentheses. + *

In negative patterns, the minimum and maximum counts are ignored; + * these are presumed to be set in the positive pattern. + * + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + */ + public void applyPattern(String pattern) { + applyPattern(pattern, false); + } + + /** + * Apply the given pattern to this Format object. The pattern + * is assumed to be in a localized notation. A pattern is a + * short-hand specification for the various formatting properties. + * These properties can also be changed individually through the + * various setter methods. + *

+ * There is no limit to integer digits set + * by this routine, since that is the typical end-user desire; + * use setMaximumInteger if you want to set a real value. + * For negative numbers, use a second pattern, separated by a semicolon + *

Example "#,#00.0#" -> 1,234.56 + *

This means a minimum of 2 integer digits, 1 fraction digit, and + * a maximum of 2 fraction digits. + *

Example: "#,#00.0#;(#,#00.0#)" for negatives in + * parentheses. + *

In negative patterns, the minimum and maximum counts are ignored; + * these are presumed to be set in the positive pattern. + * + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + */ + public void applyLocalizedPattern(String pattern) { + applyPattern(pattern, true); + } + + /** + * Does the real work of applying a pattern. + */ + private void applyPattern(String pattern, boolean localized) { + char zeroDigit = PATTERN_ZERO_DIGIT; + char groupingSeparator = PATTERN_GROUPING_SEPARATOR; + char decimalSeparator = PATTERN_DECIMAL_SEPARATOR; + char percent = PATTERN_PERCENT; + char perMill = PATTERN_PER_MILLE; + char digit = PATTERN_DIGIT; + char separator = PATTERN_SEPARATOR; + String exponent = PATTERN_EXPONENT; + char minus = PATTERN_MINUS; + if (localized) { + zeroDigit = symbols.getZeroDigit(); + groupingSeparator = symbols.getGroupingSeparator(); + decimalSeparator = symbols.getDecimalSeparator(); + percent = symbols.getPercent(); + perMill = symbols.getPerMill(); + digit = symbols.getDigit(); + separator = symbols.getPatternSeparator(); + exponent = symbols.getExponentSeparator(); + minus = symbols.getMinusSign(); + } + boolean gotNegative = false; + decimalSeparatorAlwaysShown = false; + isCurrencyFormat = false; + useExponentialNotation = false; + + // Two variables are used to record the subrange of the pattern + // occupied by phase 1. This is used during the processing of the + // second pattern (the one representing negative numbers) to ensure + // that no deviation exists in phase 1 between the two patterns. + int phaseOneStart = 0; + int phaseOneLength = 0; + + int start = 0; + for (int j = 1; j >= 0 && start < pattern.length(); --j) { + boolean inQuote = false; + StringBuffer prefix = new StringBuffer(); + StringBuffer suffix = new StringBuffer(); + int decimalPos = -1; + int multiplier = 1; + int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0; + byte groupingCount = -1; + + // The phase ranges from 0 to 2. Phase 0 is the prefix. Phase 1 is + // the section of the pattern with digits, decimal separator, + // grouping characters. Phase 2 is the suffix. In phases 0 and 2, + // percent, per mille, and currency symbols are recognized and + // translated. The separation of the characters into phases is + // strictly enforced; if phase 1 characters are to appear in the + // suffix, for example, they must be quoted. + int phase = 0; + + // The affix is either the prefix or the suffix. + StringBuffer affix = prefix; + + for (int pos = start; pos < pattern.length(); ++pos) { + char ch = pattern.charAt(pos); + switch (phase) { + case 0: + case 2: + // Process the prefix / suffix characters + if (inQuote) { + // A quote within quotes indicates either the closing + // quote or two quotes, which is a quote literal. That + // is, we have the second quote in 'do' or 'don''t'. + if (ch == QUOTE) { + if ((pos+1) < pattern.length() && + pattern.charAt(pos+1) == QUOTE) { + ++pos; + affix.append("''"); // 'don''t' + } else { + inQuote = false; // 'do' + } + continue; + } + } else { + // Process unquoted characters seen in prefix or suffix + // phase. + if (ch == digit || + ch == zeroDigit || + ch == groupingSeparator || + ch == decimalSeparator) { + phase = 1; + if (j == 1) { + phaseOneStart = pos; + } + --pos; // Reprocess this character + continue; + } else if (ch == CURRENCY_SIGN) { + // Use lookahead to determine if the currency sign + // is doubled or not. + boolean doubled = (pos + 1) < pattern.length() && + pattern.charAt(pos + 1) == CURRENCY_SIGN; + if (doubled) { // Skip over the doubled character + ++pos; + } + isCurrencyFormat = true; + affix.append(doubled ? "'\u00A4\u00A4" : "'\u00A4"); + continue; + } else if (ch == QUOTE) { + // A quote outside quotes indicates either the + // opening quote or two quotes, which is a quote + // literal. That is, we have the first quote in 'do' + // or o''clock. + if (ch == QUOTE) { + if ((pos+1) < pattern.length() && + pattern.charAt(pos+1) == QUOTE) { + ++pos; + affix.append("''"); // o''clock + } else { + inQuote = true; // 'do' + } + continue; + } + } else if (ch == separator) { + // Don't allow separators before we see digit + // characters of phase 1, and don't allow separators + // in the second pattern (j == 0). + if (phase == 0 || j == 0) { + throw new IllegalArgumentException("Unquoted special character '" + + ch + "' in pattern \"" + pattern + '"'); + } + start = pos + 1; + pos = pattern.length(); + continue; + } + + // Next handle characters which are appended directly. + else if (ch == percent) { + if (multiplier != 1) { + throw new IllegalArgumentException("Too many percent/per mille characters in pattern \"" + + pattern + '"'); + } + multiplier = 100; + affix.append("'%"); + continue; + } else if (ch == perMill) { + if (multiplier != 1) { + throw new IllegalArgumentException("Too many percent/per mille characters in pattern \"" + + pattern + '"'); + } + multiplier = 1000; + affix.append("'\u2030"); + continue; + } else if (ch == minus) { + affix.append("'-"); + continue; + } + } + // Note that if we are within quotes, or if this is an + // unquoted, non-special character, then we usually fall + // through to here. + affix.append(ch); + break; + + case 1: + // Phase one must be identical in the two sub-patterns. We + // enforce this by doing a direct comparison. While + // processing the first sub-pattern, we just record its + // length. While processing the second, we compare + // characters. + if (j == 1) { + ++phaseOneLength; + } else { + if (--phaseOneLength == 0) { + phase = 2; + affix = suffix; + } + continue; + } + + // Process the digits, decimal, and grouping characters. We + // record five pieces of information. We expect the digits + // to occur in the pattern ####0000.####, and we record the + // number of left digits, zero (central) digits, and right + // digits. The position of the last grouping character is + // recorded (should be somewhere within the first two blocks + // of characters), as is the position of the decimal point, + // if any (should be in the zero digits). If there is no + // decimal point, then there should be no right digits. + if (ch == digit) { + if (zeroDigitCount > 0) { + ++digitRightCount; + } else { + ++digitLeftCount; + } + if (groupingCount >= 0 && decimalPos < 0) { + ++groupingCount; + } + } else if (ch == zeroDigit) { + if (digitRightCount > 0) { + throw new IllegalArgumentException("Unexpected '0' in pattern \"" + + pattern + '"'); + } + ++zeroDigitCount; + if (groupingCount >= 0 && decimalPos < 0) { + ++groupingCount; + } + } else if (ch == groupingSeparator) { + groupingCount = 0; + } else if (ch == decimalSeparator) { + if (decimalPos >= 0) { + throw new IllegalArgumentException("Multiple decimal separators in pattern \"" + + pattern + '"'); + } + decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; + } else if (pattern.regionMatches(pos, exponent, 0, exponent.length())){ + if (useExponentialNotation) { + throw new IllegalArgumentException("Multiple exponential " + + "symbols in pattern \"" + pattern + '"'); + } + useExponentialNotation = true; + minExponentDigits = 0; + + // Use lookahead to parse out the exponential part + // of the pattern, then jump into phase 2. + pos = pos+exponent.length(); + while (pos < pattern.length() && + pattern.charAt(pos) == zeroDigit) { + ++minExponentDigits; + ++phaseOneLength; + ++pos; + } + + if ((digitLeftCount + zeroDigitCount) < 1 || + minExponentDigits < 1) { + throw new IllegalArgumentException("Malformed exponential " + + "pattern \"" + pattern + '"'); + } + + // Transition to phase 2 + phase = 2; + affix = suffix; + --pos; + continue; + } else { + phase = 2; + affix = suffix; + --pos; + --phaseOneLength; + continue; + } + break; + } + } + + // Handle patterns with no '0' pattern character. These patterns + // are legal, but must be interpreted. "##.###" -> "#0.###". + // ".###" -> ".0##". + /* We allow patterns of the form "####" to produce a zeroDigitCount + * of zero (got that?); although this seems like it might make it + * possible for format() to produce empty strings, format() checks + * for this condition and outputs a zero digit in this situation. + * Having a zeroDigitCount of zero yields a minimum integer digits + * of zero, which allows proper round-trip patterns. That is, we + * don't want "#" to become "#0" when toPattern() is called (even + * though that's what it really is, semantically). + */ + if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { + // Handle "###.###" and "###." and ".###" + int n = decimalPos; + if (n == 0) { // Handle ".###" + ++n; + } + digitRightCount = digitLeftCount - n; + digitLeftCount = n - 1; + zeroDigitCount = 1; + } + + // Do syntax checking on the digits. + if ((decimalPos < 0 && digitRightCount > 0) || + (decimalPos >= 0 && (decimalPos < digitLeftCount || + decimalPos > (digitLeftCount + zeroDigitCount))) || + groupingCount == 0 || inQuote) { + throw new IllegalArgumentException("Malformed pattern \"" + + pattern + '"'); + } + + if (j == 1) { + posPrefixPattern = prefix.toString(); + posSuffixPattern = suffix.toString(); + negPrefixPattern = posPrefixPattern; // assume these for now + negSuffixPattern = posSuffixPattern; + int digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount; + /* The effectiveDecimalPos is the position the decimal is at or + * would be at if there is no decimal. Note that if decimalPos<0, + * then digitTotalCount == digitLeftCount + zeroDigitCount. + */ + int effectiveDecimalPos = decimalPos >= 0 ? + decimalPos : digitTotalCount; + setMinimumIntegerDigits(effectiveDecimalPos - digitLeftCount); + setMaximumIntegerDigits(useExponentialNotation ? + digitLeftCount + getMinimumIntegerDigits() : + MAXIMUM_INTEGER_DIGITS); + setMaximumFractionDigits(decimalPos >= 0 ? + (digitTotalCount - decimalPos) : 0); + setMinimumFractionDigits(decimalPos >= 0 ? + (digitLeftCount + zeroDigitCount - decimalPos) : 0); + setGroupingUsed(groupingCount > 0); + this.groupingSize = (groupingCount > 0) ? groupingCount : 0; + this.multiplier = multiplier; + setDecimalSeparatorAlwaysShown(decimalPos == 0 || + decimalPos == digitTotalCount); + } else { + negPrefixPattern = prefix.toString(); + negSuffixPattern = suffix.toString(); + gotNegative = true; + } + } + + if (pattern.length() == 0) { + posPrefixPattern = posSuffixPattern = ""; + setMinimumIntegerDigits(0); + setMaximumIntegerDigits(MAXIMUM_INTEGER_DIGITS); + setMinimumFractionDigits(0); + setMaximumFractionDigits(MAXIMUM_FRACTION_DIGITS); + } + + // If there was no negative pattern, or if the negative pattern is + // identical to the positive pattern, then prepend the minus sign to + // the positive pattern to form the negative pattern. + if (!gotNegative || + (negPrefixPattern.equals(posPrefixPattern) + && negSuffixPattern.equals(posSuffixPattern))) { + negSuffixPattern = posSuffixPattern; + negPrefixPattern = "'-" + posPrefixPattern; + } + + expandAffixes(); + } + + /** + * Sets the maximum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 309 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMaximumIntegerDigits + */ + public void setMaximumIntegerDigits(int newValue) { + maximumIntegerDigits = Math.min(Math.max(0, newValue), MAXIMUM_INTEGER_DIGITS); + super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : maximumIntegerDigits); + if (minimumIntegerDigits > maximumIntegerDigits) { + minimumIntegerDigits = maximumIntegerDigits; + super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : minimumIntegerDigits); + } + } + + /** + * Sets the minimum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 309 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMinimumIntegerDigits + */ + public void setMinimumIntegerDigits(int newValue) { + minimumIntegerDigits = Math.min(Math.max(0, newValue), MAXIMUM_INTEGER_DIGITS); + super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : minimumIntegerDigits); + if (minimumIntegerDigits > maximumIntegerDigits) { + maximumIntegerDigits = minimumIntegerDigits; + super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : maximumIntegerDigits); + } + } + + /** + * Sets the maximum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 340 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMaximumFractionDigits + */ + public void setMaximumFractionDigits(int newValue) { + maximumFractionDigits = Math.min(Math.max(0, newValue), MAXIMUM_FRACTION_DIGITS); + super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : maximumFractionDigits); + if (minimumFractionDigits > maximumFractionDigits) { + minimumFractionDigits = maximumFractionDigits; + super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : minimumFractionDigits); + } + } + + /** + * Sets the minimum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 340 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMinimumFractionDigits + */ + public void setMinimumFractionDigits(int newValue) { + minimumFractionDigits = Math.min(Math.max(0, newValue), MAXIMUM_FRACTION_DIGITS); + super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : minimumFractionDigits); + if (minimumFractionDigits > maximumFractionDigits) { + maximumFractionDigits = minimumFractionDigits; + super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : maximumFractionDigits); + } + } + + /** + * Gets the maximum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 309 is used. + * @see #setMaximumIntegerDigits + */ + public int getMaximumIntegerDigits() { + return maximumIntegerDigits; + } + + /** + * Gets the minimum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 309 is used. + * @see #setMinimumIntegerDigits + */ + public int getMinimumIntegerDigits() { + return minimumIntegerDigits; + } + + /** + * Gets the maximum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 340 is used. + * @see #setMaximumFractionDigits + */ + public int getMaximumFractionDigits() { + return maximumFractionDigits; + } + + /** + * Gets the minimum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 340 is used. + * @see #setMinimumFractionDigits + */ + public int getMinimumFractionDigits() { + return minimumFractionDigits; + } + + /** + * Gets the currency used by this decimal format when formatting + * currency values. + * The currency is obtained by calling + * {@link DecimalFormatSymbols#getCurrency DecimalFormatSymbols.getCurrency} + * on this number format's symbols. + * + * @return the currency used by this decimal format, or null + * @since 1.4 + */ + public Currency getCurrency() { + return symbols.getCurrency(); + } + + /** + * Sets the currency used by this number format when formatting + * currency values. This does not update the minimum or maximum + * number of fraction digits used by the number format. + * The currency is set by calling + * {@link DecimalFormatSymbols#setCurrency DecimalFormatSymbols.setCurrency} + * on this number format's symbols. + * + * @param currency the new currency to be used by this decimal format + * @exception NullPointerException if currency is null + * @since 1.4 + */ + public void setCurrency(Currency currency) { + if (currency != symbols.getCurrency()) { + symbols.setCurrency(currency); + if (isCurrencyFormat) { + expandAffixes(); + } + } + } + + /** + * Gets the {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @return The RoundingMode used for this DecimalFormat. + * @see #setRoundingMode(RoundingMode) + * @since 1.6 + */ + public RoundingMode getRoundingMode() { + return roundingMode; + } + + /** + * Sets the {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @param roundingMode The RoundingMode to be used + * @see #getRoundingMode() + * @exception NullPointerException if roundingMode is null. + * @since 1.6 + */ + public void setRoundingMode(RoundingMode roundingMode) { + if (roundingMode == null) { + throw new NullPointerException(); + } + + this.roundingMode = roundingMode; + digitList.setRoundingMode(roundingMode); + } + + /** + * Adjusts the minimum and maximum fraction digits to values that + * are reasonable for the currency's default fraction digits. + */ + void adjustForCurrencyDefaultFractionDigits() { + Currency currency = symbols.getCurrency(); + if (currency == null) { + try { + currency = Currency.getInstance(symbols.getInternationalCurrencySymbol()); + } catch (IllegalArgumentException e) { + } + } + if (currency != null) { + int digits = currency.getDefaultFractionDigits(); + if (digits != -1) { + int oldMinDigits = getMinimumFractionDigits(); + // Common patterns are "#.##", "#.00", "#". + // Try to adjust all of them in a reasonable way. + if (oldMinDigits == getMaximumFractionDigits()) { + setMinimumFractionDigits(digits); + setMaximumFractionDigits(digits); + } else { + setMinimumFractionDigits(Math.min(digits, oldMinDigits)); + setMaximumFractionDigits(digits); + } + } + } + } + + /** + * Reads the default serializable fields from the stream and performs + * validations and adjustments for older serialized versions. The + * validations and adjustments are: + *

    + *
  1. + * Verify that the superclass's digit count fields correctly reflect + * the limits imposed on formatting numbers other than + * BigInteger and BigDecimal objects. These + * limits are stored in the superclass for serialization compatibility + * with older versions, while the limits for BigInteger and + * BigDecimal objects are kept in this class. + * If, in the superclass, the minimum or maximum integer digit count is + * larger than DOUBLE_INTEGER_DIGITS or if the minimum or + * maximum fraction digit count is larger than + * DOUBLE_FRACTION_DIGITS, then the stream data is invalid + * and this method throws an InvalidObjectException. + *
  2. + * If serialVersionOnStream is less than 4, initialize + * roundingMode to {@link java.math.RoundingMode#HALF_EVEN + * RoundingMode.HALF_EVEN}. This field is new with version 4. + *
  3. + * If serialVersionOnStream is less than 3, then call + * the setters for the minimum and maximum integer and fraction digits with + * the values of the corresponding superclass getters to initialize the + * fields in this class. The fields in this class are new with version 3. + *
  4. + * If serialVersionOnStream is less than 1, indicating that + * the stream was written by JDK 1.1, initialize + * useExponentialNotation + * to false, since it was not present in JDK 1.1. + *
  5. + * Set serialVersionOnStream to the maximum allowed value so + * that default serialization will work properly if this object is streamed + * out again. + *
+ * + *

Stream versions older than 2 will not have the affix pattern variables + * posPrefixPattern etc. As a result, they will be initialized + * to null, which means the affix strings will be taken as + * literal values. This is exactly what we want, since that corresponds to + * the pre-version-2 behavior. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + digitList = new DigitList(); + + if (serialVersionOnStream < 4) { + setRoundingMode(RoundingMode.HALF_EVEN); + } + // We only need to check the maximum counts because NumberFormat + // .readObject has already ensured that the maximum is greater than the + // minimum count. + if (super.getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS || + super.getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) { + throw new InvalidObjectException("Digit count out of range"); + } + if (serialVersionOnStream < 3) { + setMaximumIntegerDigits(super.getMaximumIntegerDigits()); + setMinimumIntegerDigits(super.getMinimumIntegerDigits()); + setMaximumFractionDigits(super.getMaximumFractionDigits()); + setMinimumFractionDigits(super.getMinimumFractionDigits()); + } + if (serialVersionOnStream < 1) { + // Didn't have exponential fields + useExponentialNotation = false; + } + serialVersionOnStream = currentSerialVersion; + } + + //---------------------------------------------------------------------- + // INSTANCE VARIABLES + //---------------------------------------------------------------------- + + private transient DigitList digitList = new DigitList(); + + /** + * The symbol used as a prefix when formatting positive numbers, e.g. "+". + * + * @serial + * @see #getPositivePrefix + */ + private String positivePrefix = ""; + + /** + * The symbol used as a suffix when formatting positive numbers. + * This is often an empty string. + * + * @serial + * @see #getPositiveSuffix + */ + private String positiveSuffix = ""; + + /** + * The symbol used as a prefix when formatting negative numbers, e.g. "-". + * + * @serial + * @see #getNegativePrefix + */ + private String negativePrefix = "-"; + + /** + * The symbol used as a suffix when formatting negative numbers. + * This is often an empty string. + * + * @serial + * @see #getNegativeSuffix + */ + private String negativeSuffix = ""; + + /** + * The prefix pattern for non-negative numbers. This variable corresponds + * to positivePrefix. + * + *

This pattern is expanded by the method expandAffix() to + * positivePrefix to update the latter to reflect changes in + * symbols. If this variable is null then + * positivePrefix is taken as a literal value that does not + * change when symbols changes. This variable is always + * null for DecimalFormat objects older than + * stream version 2 restored from stream. + * + * @serial + * @since 1.3 + */ + private String posPrefixPattern; + + /** + * The suffix pattern for non-negative numbers. This variable corresponds + * to positiveSuffix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String posSuffixPattern; + + /** + * The prefix pattern for negative numbers. This variable corresponds + * to negativePrefix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String negPrefixPattern; + + /** + * The suffix pattern for negative numbers. This variable corresponds + * to negativeSuffix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String negSuffixPattern; + + /** + * The multiplier for use in percent, per mille, etc. + * + * @serial + * @see #getMultiplier + */ + private int multiplier = 1; + + /** + * The number of digits between grouping separators in the integer + * portion of a number. Must be greater than 0 if + * NumberFormat.groupingUsed is true. + * + * @serial + * @see #getGroupingSize + * @see java.text.NumberFormat#isGroupingUsed + */ + private byte groupingSize = 3; // invariant, > 0 if useThousands + + /** + * If true, forces the decimal separator to always appear in a formatted + * number, even if the fractional part of the number is zero. + * + * @serial + * @see #isDecimalSeparatorAlwaysShown + */ + private boolean decimalSeparatorAlwaysShown = false; + + /** + * If true, parse returns BigDecimal wherever possible. + * + * @serial + * @see #isParseBigDecimal + * @since 1.5 + */ + private boolean parseBigDecimal = false; + + + /** + * True if this object represents a currency format. This determines + * whether the monetary decimal separator is used instead of the normal one. + */ + private transient boolean isCurrencyFormat = false; + + /** + * The DecimalFormatSymbols object used by this format. + * It contains the symbols used to format numbers, e.g. the grouping separator, + * decimal separator, and so on. + * + * @serial + * @see #setDecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols(); + + /** + * True to force the use of exponential (i.e. scientific) notation when formatting + * numbers. + * + * @serial + * @since 1.2 + */ + private boolean useExponentialNotation; // Newly persistent in the Java 2 platform v.1.2 + + /** + * FieldPositions describing the positive prefix String. This is + * lazily created. Use getPositivePrefixFieldPositions + * when needed. + */ + private transient FieldPosition[] positivePrefixFieldPositions; + + /** + * FieldPositions describing the positive suffix String. This is + * lazily created. Use getPositiveSuffixFieldPositions + * when needed. + */ + private transient FieldPosition[] positiveSuffixFieldPositions; + + /** + * FieldPositions describing the negative prefix String. This is + * lazily created. Use getNegativePrefixFieldPositions + * when needed. + */ + private transient FieldPosition[] negativePrefixFieldPositions; + + /** + * FieldPositions describing the negative suffix String. This is + * lazily created. Use getNegativeSuffixFieldPositions + * when needed. + */ + private transient FieldPosition[] negativeSuffixFieldPositions; + + /** + * The minimum number of digits used to display the exponent when a number is + * formatted in exponential notation. This field is ignored if + * useExponentialNotation is not true. + * + * @serial + * @since 1.2 + */ + private byte minExponentDigits; // Newly persistent in the Java 2 platform v.1.2 + + /** + * The maximum number of digits allowed in the integer portion of a + * BigInteger or BigDecimal number. + * maximumIntegerDigits must be greater than or equal to + * minimumIntegerDigits. + * + * @serial + * @see #getMaximumIntegerDigits + * @since 1.5 + */ + private int maximumIntegerDigits = super.getMaximumIntegerDigits(); + + /** + * The minimum number of digits allowed in the integer portion of a + * BigInteger or BigDecimal number. + * minimumIntegerDigits must be less than or equal to + * maximumIntegerDigits. + * + * @serial + * @see #getMinimumIntegerDigits + * @since 1.5 + */ + private int minimumIntegerDigits = super.getMinimumIntegerDigits(); + + /** + * The maximum number of digits allowed in the fractional portion of a + * BigInteger or BigDecimal number. + * maximumFractionDigits must be greater than or equal to + * minimumFractionDigits. + * + * @serial + * @see #getMaximumFractionDigits + * @since 1.5 + */ + private int maximumFractionDigits = super.getMaximumFractionDigits(); + + /** + * The minimum number of digits allowed in the fractional portion of a + * BigInteger or BigDecimal number. + * minimumFractionDigits must be less than or equal to + * maximumFractionDigits. + * + * @serial + * @see #getMinimumFractionDigits + * @since 1.5 + */ + private int minimumFractionDigits = super.getMinimumFractionDigits(); + + /** + * The {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @serial + * @since 1.6 + */ + private RoundingMode roundingMode = RoundingMode.HALF_EVEN; + + //---------------------------------------------------------------------- + + static final int currentSerialVersion = 4; + + /** + * The internal serial version which says which version was written. + * Possible values are: + *

    + *
  • 0 (default): versions before the Java 2 platform v1.2 + *
  • 1: version for 1.2, which includes the two new fields + * useExponentialNotation and + * minExponentDigits. + *
  • 2: version for 1.3 and later, which adds four new fields: + * posPrefixPattern, posSuffixPattern, + * negPrefixPattern, and negSuffixPattern. + *
  • 3: version for 1.5 and later, which adds five new fields: + * maximumIntegerDigits, + * minimumIntegerDigits, + * maximumFractionDigits, + * minimumFractionDigits, and + * parseBigDecimal. + *
  • 4: version for 1.6 and later, which adds one new field: + * roundingMode. + *
+ * @since 1.2 + * @serial + */ + private int serialVersionOnStream = currentSerialVersion; + + //---------------------------------------------------------------------- + // CONSTANTS + //---------------------------------------------------------------------- + + // Constants for characters used in programmatic (unlocalized) patterns. + private static final char PATTERN_ZERO_DIGIT = '0'; + private static final char PATTERN_GROUPING_SEPARATOR = ','; + private static final char PATTERN_DECIMAL_SEPARATOR = '.'; + private static final char PATTERN_PER_MILLE = '\u2030'; + private static final char PATTERN_PERCENT = '%'; + private static final char PATTERN_DIGIT = '#'; + private static final char PATTERN_SEPARATOR = ';'; + private static final String PATTERN_EXPONENT = "E"; + private static final char PATTERN_MINUS = '-'; + + /** + * The CURRENCY_SIGN is the standard Unicode symbol for currency. It + * is used in patterns and substituted with either the currency symbol, + * or if it is doubled, with the international currency symbol. If the + * CURRENCY_SIGN is seen in a pattern, then the decimal separator is + * replaced with the monetary decimal separator. + * + * The CURRENCY_SIGN is not localized. + */ + private static final char CURRENCY_SIGN = '\u00A4'; + + private static final char QUOTE = '\''; + + private static FieldPosition[] EmptyFieldPositionArray = new FieldPosition[0]; + + // Upper limit on integer and fraction digits for a Java double + static final int DOUBLE_INTEGER_DIGITS = 309; + static final int DOUBLE_FRACTION_DIGITS = 340; + + // Upper limit on integer and fraction digits for BigDecimal and BigInteger + static final int MAXIMUM_INTEGER_DIGITS = Integer.MAX_VALUE; + static final int MAXIMUM_FRACTION_DIGITS = Integer.MAX_VALUE; + + // Proclaim JDK 1.1 serial compatibility. + static final long serialVersionUID = 864413376551465018L; + + /** + * Cache to hold the NumberPattern of a Locale. + */ + private static final ConcurrentMap cachedLocaleData + = new ConcurrentHashMap(3); +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/DecimalFormatSymbols.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DecimalFormatSymbols.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,837 @@ +/* + * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.text.spi.DecimalFormatSymbolsProvider; +import java.util.Currency; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; + +import sun.util.LocaleServiceProviderPool; +import sun.util.resources.LocaleData; + +/** + * This class represents the set of symbols (such as the decimal separator, + * the grouping separator, and so on) needed by DecimalFormat + * to format numbers. DecimalFormat creates for itself an instance of + * DecimalFormatSymbols from its locale data. If you need to change any + * of these symbols, you can get the DecimalFormatSymbols object from + * your DecimalFormat and modify it. + * + * @see java.util.Locale + * @see DecimalFormat + * @author Mark Davis + * @author Alan Liu + */ + +public class DecimalFormatSymbols implements Cloneable, Serializable { + + /** + * Create a DecimalFormatSymbols object for the default locale. + * This constructor can only construct instances for the locales + * supported by the Java runtime environment, not for those + * supported by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} + * implementations. For full locale coverage, use the + * {@link #getInstance(Locale) getInstance} method. + */ + public DecimalFormatSymbols() { + initialize( Locale.getDefault(Locale.Category.FORMAT) ); + } + + /** + * Create a DecimalFormatSymbols object for the given locale. + * This constructor can only construct instances for the locales + * supported by the Java runtime environment, not for those + * supported by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} + * implementations. For full locale coverage, use the + * {@link #getInstance(Locale) getInstance} method. + * + * @exception NullPointerException if locale is null + */ + public DecimalFormatSymbols( Locale locale ) { + initialize( locale ); + } + + /** + * Returns an array of all locales for which the + * getInstance methods of this class can return + * localized instances. + * The returned array represents the union of locales supported by the Java + * runtime and by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider DecimalFormatSymbolsProvider} + * implementations. It must contain at least a Locale + * instance equal to {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * DecimalFormatSymbols instances are available. + * @since 1.6 + */ + public static Locale[] getAvailableLocales() { + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(DecimalFormatSymbolsProvider.class); + return pool.getAvailableLocales(); + } + + /** + * Gets the DecimalFormatSymbols instance for the default + * locale. This method provides access to DecimalFormatSymbols + * instances for locales supported by the Java runtime itself as well + * as for those supported by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider + * DecimalFormatSymbolsProvider} implementations. + * @return a DecimalFormatSymbols instance. + * @since 1.6 + */ + public static final DecimalFormatSymbols getInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets the DecimalFormatSymbols instance for the specified + * locale. This method provides access to DecimalFormatSymbols + * instances for locales supported by the Java runtime itself as well + * as for those supported by installed + * {@link java.text.spi.DecimalFormatSymbolsProvider + * DecimalFormatSymbolsProvider} implementations. + * @param locale the desired locale. + * @return a DecimalFormatSymbols instance. + * @exception NullPointerException if locale is null + * @since 1.6 + */ + public static final DecimalFormatSymbols getInstance(Locale locale) { + + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(DecimalFormatSymbolsProvider.class); + if (pool.hasProviders()) { + DecimalFormatSymbols providersInstance = pool.getLocalizedObject( + DecimalFormatSymbolsGetter.INSTANCE, locale); + if (providersInstance != null) { + return providersInstance; + } + } + + return new DecimalFormatSymbols(locale); + } + + /** + * Gets the character used for zero. Different for Arabic, etc. + */ + public char getZeroDigit() { + return zeroDigit; + } + + /** + * Sets the character used for zero. Different for Arabic, etc. + */ + public void setZeroDigit(char zeroDigit) { + this.zeroDigit = zeroDigit; + } + + /** + * Gets the character used for thousands separator. Different for French, etc. + */ + public char getGroupingSeparator() { + return groupingSeparator; + } + + /** + * Sets the character used for thousands separator. Different for French, etc. + */ + public void setGroupingSeparator(char groupingSeparator) { + this.groupingSeparator = groupingSeparator; + } + + /** + * Gets the character used for decimal sign. Different for French, etc. + */ + public char getDecimalSeparator() { + return decimalSeparator; + } + + /** + * Sets the character used for decimal sign. Different for French, etc. + */ + public void setDecimalSeparator(char decimalSeparator) { + this.decimalSeparator = decimalSeparator; + } + + /** + * Gets the character used for per mille sign. Different for Arabic, etc. + */ + public char getPerMill() { + return perMill; + } + + /** + * Sets the character used for per mille sign. Different for Arabic, etc. + */ + public void setPerMill(char perMill) { + this.perMill = perMill; + } + + /** + * Gets the character used for percent sign. Different for Arabic, etc. + */ + public char getPercent() { + return percent; + } + + /** + * Sets the character used for percent sign. Different for Arabic, etc. + */ + public void setPercent(char percent) { + this.percent = percent; + } + + /** + * Gets the character used for a digit in a pattern. + */ + public char getDigit() { + return digit; + } + + /** + * Sets the character used for a digit in a pattern. + */ + public void setDigit(char digit) { + this.digit = digit; + } + + /** + * Gets the character used to separate positive and negative subpatterns + * in a pattern. + */ + public char getPatternSeparator() { + return patternSeparator; + } + + /** + * Sets the character used to separate positive and negative subpatterns + * in a pattern. + */ + public void setPatternSeparator(char patternSeparator) { + this.patternSeparator = patternSeparator; + } + + /** + * Gets the string used to represent infinity. Almost always left + * unchanged. + */ + public String getInfinity() { + return infinity; + } + + /** + * Sets the string used to represent infinity. Almost always left + * unchanged. + */ + public void setInfinity(String infinity) { + this.infinity = infinity; + } + + /** + * Gets the string used to represent "not a number". Almost always left + * unchanged. + */ + public String getNaN() { + return NaN; + } + + /** + * Sets the string used to represent "not a number". Almost always left + * unchanged. + */ + public void setNaN(String NaN) { + this.NaN = NaN; + } + + /** + * Gets the character used to represent minus sign. If no explicit + * negative format is specified, one is formed by prefixing + * minusSign to the positive format. + */ + public char getMinusSign() { + return minusSign; + } + + /** + * Sets the character used to represent minus sign. If no explicit + * negative format is specified, one is formed by prefixing + * minusSign to the positive format. + */ + public void setMinusSign(char minusSign) { + this.minusSign = minusSign; + } + + /** + * Returns the currency symbol for the currency of these + * DecimalFormatSymbols in their locale. + * @since 1.2 + */ + public String getCurrencySymbol() + { + return currencySymbol; + } + + /** + * Sets the currency symbol for the currency of these + * DecimalFormatSymbols in their locale. + * @since 1.2 + */ + public void setCurrencySymbol(String currency) + { + currencySymbol = currency; + } + + /** + * Returns the ISO 4217 currency code of the currency of these + * DecimalFormatSymbols. + * @since 1.2 + */ + public String getInternationalCurrencySymbol() + { + return intlCurrencySymbol; + } + + /** + * Sets the ISO 4217 currency code of the currency of these + * DecimalFormatSymbols. + * If the currency code is valid (as defined by + * {@link java.util.Currency#getInstance(java.lang.String) Currency.getInstance}), + * this also sets the currency attribute to the corresponding Currency + * instance and the currency symbol attribute to the currency's symbol + * in the DecimalFormatSymbols' locale. If the currency code is not valid, + * then the currency attribute is set to null and the currency symbol + * attribute is not modified. + * + * @see #setCurrency + * @see #setCurrencySymbol + * @since 1.2 + */ + public void setInternationalCurrencySymbol(String currencyCode) + { + intlCurrencySymbol = currencyCode; + currency = null; + if (currencyCode != null) { + try { + currency = Currency.getInstance(currencyCode); + currencySymbol = currency.getSymbol(); + } catch (IllegalArgumentException e) { + } + } + } + + /** + * Gets the currency of these DecimalFormatSymbols. May be null if the + * currency symbol attribute was previously set to a value that's not + * a valid ISO 4217 currency code. + * + * @return the currency used, or null + * @since 1.4 + */ + public Currency getCurrency() { + return currency; + } + + /** + * Sets the currency of these DecimalFormatSymbols. + * This also sets the currency symbol attribute to the currency's symbol + * in the DecimalFormatSymbols' locale, and the international currency + * symbol attribute to the currency's ISO 4217 currency code. + * + * @param currency the new currency to be used + * @exception NullPointerException if currency is null + * @since 1.4 + * @see #setCurrencySymbol + * @see #setInternationalCurrencySymbol + */ + public void setCurrency(Currency currency) { + if (currency == null) { + throw new NullPointerException(); + } + this.currency = currency; + intlCurrencySymbol = currency.getCurrencyCode(); + currencySymbol = currency.getSymbol(locale); + } + + + /** + * Returns the monetary decimal separator. + * @since 1.2 + */ + public char getMonetaryDecimalSeparator() + { + return monetarySeparator; + } + + /** + * Sets the monetary decimal separator. + * @since 1.2 + */ + public void setMonetaryDecimalSeparator(char sep) + { + monetarySeparator = sep; + } + + //------------------------------------------------------------ + // BEGIN Package Private methods ... to be made public later + //------------------------------------------------------------ + + /** + * Returns the character used to separate the mantissa from the exponent. + */ + char getExponentialSymbol() + { + return exponential; + } + /** + * Returns the string used to separate the mantissa from the exponent. + * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. + * + * @return the exponent separator string + * @see #setExponentSeparator(java.lang.String) + * @since 1.6 + */ + public String getExponentSeparator() + { + return exponentialSeparator; + } + + /** + * Sets the character used to separate the mantissa from the exponent. + */ + void setExponentialSymbol(char exp) + { + exponential = exp; + } + + /** + * Sets the string used to separate the mantissa from the exponent. + * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. + * + * @param exp the exponent separator string + * @exception NullPointerException if exp is null + * @see #getExponentSeparator() + * @since 1.6 + */ + public void setExponentSeparator(String exp) + { + if (exp == null) { + throw new NullPointerException(); + } + exponentialSeparator = exp; + } + + + //------------------------------------------------------------ + // END Package Private methods ... to be made public later + //------------------------------------------------------------ + + /** + * Standard override. + */ + public Object clone() { + try { + return (DecimalFormatSymbols)super.clone(); + // other fields are bit-copied + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Override equals. + */ + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (getClass() != obj.getClass()) return false; + DecimalFormatSymbols other = (DecimalFormatSymbols) obj; + return (zeroDigit == other.zeroDigit && + groupingSeparator == other.groupingSeparator && + decimalSeparator == other.decimalSeparator && + percent == other.percent && + perMill == other.perMill && + digit == other.digit && + minusSign == other.minusSign && + patternSeparator == other.patternSeparator && + infinity.equals(other.infinity) && + NaN.equals(other.NaN) && + currencySymbol.equals(other.currencySymbol) && + intlCurrencySymbol.equals(other.intlCurrencySymbol) && + currency == other.currency && + monetarySeparator == other.monetarySeparator && + exponentialSeparator.equals(other.exponentialSeparator) && + locale.equals(other.locale)); + } + + /** + * Override hashCode. + */ + public int hashCode() { + int result = zeroDigit; + result = result * 37 + groupingSeparator; + result = result * 37 + decimalSeparator; + return result; + } + + /** + * Initializes the symbols from the FormatData resource bundle. + */ + private void initialize( Locale locale ) { + this.locale = locale; + + // get resource bundle data - try the cache first + boolean needCacheUpdate = false; + Object[] data = cachedLocaleData.get(locale); + if (data == null) { /* cache miss */ + // When numbering system is thai (Locale's extension contains u-nu-thai), + // we read the data from th_TH_TH. + Locale lookupLocale = locale; + String numberType = locale.getUnicodeLocaleType("nu"); + if (numberType != null && numberType.equals("thai")) { + lookupLocale = new Locale("th", "TH", "TH"); + } + data = new Object[3]; + ResourceBundle rb = LocaleData.getNumberFormatData(lookupLocale); + data[0] = rb.getStringArray("NumberElements"); + needCacheUpdate = true; + } + + String[] numberElements = (String[]) data[0]; + + decimalSeparator = numberElements[0].charAt(0); + groupingSeparator = numberElements[1].charAt(0); + patternSeparator = numberElements[2].charAt(0); + percent = numberElements[3].charAt(0); + zeroDigit = numberElements[4].charAt(0); //different for Arabic,etc. + digit = numberElements[5].charAt(0); + minusSign = numberElements[6].charAt(0); + exponential = numberElements[7].charAt(0); + exponentialSeparator = numberElements[7]; //string representation new since 1.6 + perMill = numberElements[8].charAt(0); + infinity = numberElements[9]; + NaN = numberElements[10]; + + // Try to obtain the currency used in the locale's country. + // Check for empty country string separately because it's a valid + // country ID for Locale (and used for the C locale), but not a valid + // ISO 3166 country code, and exceptions are expensive. + if (!"".equals(locale.getCountry())) { + try { + currency = Currency.getInstance(locale); + } catch (IllegalArgumentException e) { + // use default values below for compatibility + } + } + if (currency != null) { + intlCurrencySymbol = currency.getCurrencyCode(); + if (data[1] != null && data[1] == intlCurrencySymbol) { + currencySymbol = (String) data[2]; + } else { + currencySymbol = currency.getSymbol(locale); + data[1] = intlCurrencySymbol; + data[2] = currencySymbol; + needCacheUpdate = true; + } + } else { + // default values + intlCurrencySymbol = "XXX"; + try { + currency = Currency.getInstance(intlCurrencySymbol); + } catch (IllegalArgumentException e) { + } + currencySymbol = "\u00A4"; + } + // Currently the monetary decimal separator is the same as the + // standard decimal separator for all locales that we support. + // If that changes, add a new entry to NumberElements. + monetarySeparator = decimalSeparator; + + if (needCacheUpdate) { + cachedLocaleData.putIfAbsent(locale, data); + } + } + + /** + * Reads the default serializable fields, provides default values for objects + * in older serial versions, and initializes non-serializable fields. + * If serialVersionOnStream + * is less than 1, initializes monetarySeparator to be + * the same as decimalSeparator and exponential + * to be 'E'. + * If serialVersionOnStream is less than 2, + * initializes localeto the root locale, and initializes + * If serialVersionOnStream is less than 3, it initializes + * exponentialSeparator using exponential. + * Sets serialVersionOnStream back to the maximum allowed value so that + * default serialization will work properly if this object is streamed out again. + * Initializes the currency from the intlCurrencySymbol field. + * + * @since JDK 1.1.6 + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) { + // Didn't have monetarySeparator or exponential field; + // use defaults. + monetarySeparator = decimalSeparator; + exponential = 'E'; + } + if (serialVersionOnStream < 2) { + // didn't have locale; use root locale + locale = Locale.ROOT; + } + if (serialVersionOnStream < 3) { + // didn't have exponentialSeparator. Create one using exponential + exponentialSeparator = Character.toString(exponential); + } + serialVersionOnStream = currentSerialVersion; + + if (intlCurrencySymbol != null) { + try { + currency = Currency.getInstance(intlCurrencySymbol); + } catch (IllegalArgumentException e) { + } + } + } + + /** + * Character used for zero. + * + * @serial + * @see #getZeroDigit + */ + private char zeroDigit; + + /** + * Character used for thousands separator. + * + * @serial + * @see #getGroupingSeparator + */ + private char groupingSeparator; + + /** + * Character used for decimal sign. + * + * @serial + * @see #getDecimalSeparator + */ + private char decimalSeparator; + + /** + * Character used for per mille sign. + * + * @serial + * @see #getPerMill + */ + private char perMill; + + /** + * Character used for percent sign. + * @serial + * @see #getPercent + */ + private char percent; + + /** + * Character used for a digit in a pattern. + * + * @serial + * @see #getDigit + */ + private char digit; + + /** + * Character used to separate positive and negative subpatterns + * in a pattern. + * + * @serial + * @see #getPatternSeparator + */ + private char patternSeparator; + + /** + * String used to represent infinity. + * @serial + * @see #getInfinity + */ + private String infinity; + + /** + * String used to represent "not a number". + * @serial + * @see #getNaN + */ + private String NaN; + + /** + * Character used to represent minus sign. + * @serial + * @see #getMinusSign + */ + private char minusSign; + + /** + * String denoting the local currency, e.g. "$". + * @serial + * @see #getCurrencySymbol + */ + private String currencySymbol; + + /** + * ISO 4217 currency code denoting the local currency, e.g. "USD". + * @serial + * @see #getInternationalCurrencySymbol + */ + private String intlCurrencySymbol; + + /** + * The decimal separator used when formatting currency values. + * @serial + * @since JDK 1.1.6 + * @see #getMonetaryDecimalSeparator + */ + private char monetarySeparator; // Field new in JDK 1.1.6 + + /** + * The character used to distinguish the exponent in a number formatted + * in exponential notation, e.g. 'E' for a number such as "1.23E45". + *

+ * Note that the public API provides no way to set this field, + * even though it is supported by the implementation and the stream format. + * The intent is that this will be added to the API in the future. + * + * @serial + * @since JDK 1.1.6 + */ + private char exponential; // Field new in JDK 1.1.6 + + /** + * The string used to separate the mantissa from the exponent. + * Examples: "x10^" for 1.23x10^4, "E" for 1.23E4. + *

+ * If both exponential and exponentialSeparator + * exist, this exponentialSeparator has the precedence. + * + * @serial + * @since 1.6 + */ + private String exponentialSeparator; // Field new in JDK 1.6 + + /** + * The locale of these currency format symbols. + * + * @serial + * @since 1.4 + */ + private Locale locale; + + // currency; only the ISO code is serialized. + private transient Currency currency; + + // Proclaim JDK 1.1 FCS compatibility + static final long serialVersionUID = 5772796243397350300L; + + // The internal serial version which says which version was written + // - 0 (default) for version up to JDK 1.1.5 + // - 1 for version from JDK 1.1.6, which includes two new fields: + // monetarySeparator and exponential. + // - 2 for version from J2SE 1.4, which includes locale field. + // - 3 for version from J2SE 1.6, which includes exponentialSeparator field. + private static final int currentSerialVersion = 3; + + /** + * Describes the version of DecimalFormatSymbols present on the stream. + * Possible values are: + *

    + *
  • 0 (or uninitialized): versions prior to JDK 1.1.6. + * + *
  • 1: Versions written by JDK 1.1.6 or later, which include + * two new fields: monetarySeparator and exponential. + *
  • 2: Versions written by J2SE 1.4 or later, which include a + * new locale field. + *
  • 3: Versions written by J2SE 1.6 or later, which include a + * new exponentialSeparator field. + *
+ * When streaming out a DecimalFormatSymbols, the most recent format + * (corresponding to the highest allowable serialVersionOnStream) + * is always written. + * + * @serial + * @since JDK 1.1.6 + */ + private int serialVersionOnStream = currentSerialVersion; + + /** + * cache to hold the NumberElements and the Currency + * of a Locale. + */ + private static final ConcurrentHashMap cachedLocaleData = new ConcurrentHashMap(3); + + /** + * Obtains a DecimalFormatSymbols instance from a DecimalFormatSymbolsProvider + * implementation. + */ + private static class DecimalFormatSymbolsGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final DecimalFormatSymbolsGetter INSTANCE = + new DecimalFormatSymbolsGetter(); + + public DecimalFormatSymbols getObject( + DecimalFormatSymbolsProvider decimalFormatSymbolsProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 0; + return decimalFormatSymbolsProvider.getInstance(locale); + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/DigitList.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DigitList.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,715 @@ +/* + * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * Digit List. Private to DecimalFormat. + * Handles the transcoding + * between numeric values and strings of characters. Only handles + * non-negative numbers. The division of labor between DigitList and + * DecimalFormat is that DigitList handles the radix 10 representation + * issues; DecimalFormat handles the locale-specific issues such as + * positive/negative, grouping, decimal point, currency, and so on. + * + * A DigitList is really a representation of a floating point value. + * It may be an integer value; we assume that a double has sufficient + * precision to represent all digits of a long. + * + * The DigitList representation consists of a string of characters, + * which are the digits radix 10, from '0' to '9'. It also has a radix + * 10 exponent associated with it. The value represented by a DigitList + * object can be computed by mulitplying the fraction f, where 0 <= f < 1, + * derived by placing all the digits of the list to the right of the + * decimal point, by 10^exponent. + * + * @see Locale + * @see Format + * @see NumberFormat + * @see DecimalFormat + * @see ChoiceFormat + * @see MessageFormat + * @author Mark Davis, Alan Liu + */ +final class DigitList implements Cloneable { + /** + * The maximum number of significant digits in an IEEE 754 double, that + * is, in a Java double. This must not be increased, or garbage digits + * will be generated, and should not be decreased, or accuracy will be lost. + */ + public static final int MAX_COUNT = 19; // == Long.toString(Long.MAX_VALUE).length() + + /** + * These data members are intentionally public and can be set directly. + * + * The value represented is given by placing the decimal point before + * digits[decimalAt]. If decimalAt is < 0, then leading zeros between + * the decimal point and the first nonzero digit are implied. If decimalAt + * is > count, then trailing zeros between the digits[count-1] and the + * decimal point are implied. + * + * Equivalently, the represented value is given by f * 10^decimalAt. Here + * f is a value 0.1 <= f < 1 arrived at by placing the digits in Digits to + * the right of the decimal. + * + * DigitList is normalized, so if it is non-zero, figits[0] is non-zero. We + * don't allow denormalized numbers because our exponent is effectively of + * unlimited magnitude. The count value contains the number of significant + * digits present in digits[]. + * + * Zero is represented by any DigitList with count == 0 or with each digits[i] + * for all i <= count == '0'. + */ + public int decimalAt = 0; + public int count = 0; + public char[] digits = new char[MAX_COUNT]; + + private char[] data; + private RoundingMode roundingMode = RoundingMode.HALF_EVEN; + private boolean isNegative = false; + + /** + * Return true if the represented number is zero. + */ + boolean isZero() { + for (int i=0; i < count; ++i) { + if (digits[i] != '0') { + return false; + } + } + return true; + } + + /** + * Set the rounding mode + */ + void setRoundingMode(RoundingMode r) { + roundingMode = r; + } + + /** + * Clears out the digits. + * Use before appending them. + * Typically, you set a series of digits with append, then at the point + * you hit the decimal point, you set myDigitList.decimalAt = myDigitList.count; + * then go on appending digits. + */ + public void clear () { + decimalAt = 0; + count = 0; + } + + /** + * Appends a digit to the list, extending the list when necessary. + */ + public void append(char digit) { + if (count == digits.length) { + char[] data = new char[count + 100]; + System.arraycopy(digits, 0, data, 0, count); + digits = data; + } + digits[count++] = digit; + } + + /** + * Utility routine to get the value of the digit list + * If (count == 0) this throws a NumberFormatException, which + * mimics Long.parseLong(). + */ + public final double getDouble() { + if (count == 0) { + return 0.0; + } + + StringBuffer temp = getStringBuffer(); + temp.append('.'); + temp.append(digits, 0, count); + temp.append('E'); + temp.append(decimalAt); + return Double.parseDouble(temp.toString()); + } + + /** + * Utility routine to get the value of the digit list. + * If (count == 0) this returns 0, unlike Long.parseLong(). + */ + public final long getLong() { + // for now, simple implementation; later, do proper IEEE native stuff + + if (count == 0) { + return 0; + } + + // We have to check for this, because this is the one NEGATIVE value + // we represent. If we tried to just pass the digits off to parseLong, + // we'd get a parse failure. + if (isLongMIN_VALUE()) { + return Long.MIN_VALUE; + } + + StringBuffer temp = getStringBuffer(); + temp.append(digits, 0, count); + for (int i = count; i < decimalAt; ++i) { + temp.append('0'); + } + return Long.parseLong(temp.toString()); + } + + public final BigDecimal getBigDecimal() { + if (count == 0) { + if (decimalAt == 0) { + return BigDecimal.ZERO; + } else { + return new BigDecimal("0E" + decimalAt); + } + } + + if (decimalAt == count) { + return new BigDecimal(digits, 0, count); + } else { + return new BigDecimal(digits, 0, count).scaleByPowerOfTen(decimalAt - count); + } + } + + /** + * Return true if the number represented by this object can fit into + * a long. + * @param isPositive true if this number should be regarded as positive + * @param ignoreNegativeZero true if -0 should be regarded as identical to + * +0; otherwise they are considered distinct + * @return true if this number fits into a Java long + */ + boolean fitsIntoLong(boolean isPositive, boolean ignoreNegativeZero) { + // Figure out if the result will fit in a long. We have to + // first look for nonzero digits after the decimal point; + // then check the size. If the digit count is 18 or less, then + // the value can definitely be represented as a long. If it is 19 + // then it may be too large. + + // Trim trailing zeros. This does not change the represented value. + while (count > 0 && digits[count - 1] == '0') { + --count; + } + + if (count == 0) { + // Positive zero fits into a long, but negative zero can only + // be represented as a double. - bug 4162852 + return isPositive || ignoreNegativeZero; + } + + if (decimalAt < count || decimalAt > MAX_COUNT) { + return false; + } + + if (decimalAt < MAX_COUNT) return true; + + // At this point we have decimalAt == count, and count == MAX_COUNT. + // The number will overflow if it is larger than 9223372036854775807 + // or smaller than -9223372036854775808. + for (int i=0; i max) return false; + if (dig < max) return true; + } + + // At this point the first count digits match. If decimalAt is less + // than count, then the remaining digits are zero, and we return true. + if (count < decimalAt) return true; + + // Now we have a representation of Long.MIN_VALUE, without the leading + // negative sign. If this represents a positive value, then it does + // not fit; otherwise it fits. + return !isPositive; + } + + /** + * Set the digit list to a representation of the given double value. + * This method supports fixed-point notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be Inf, -Inf, Nan, + * or a value <= 0. + * @param maximumFractionDigits The most fractional digits which should + * be converted. + */ + public final void set(boolean isNegative, double source, int maximumFractionDigits) { + set(isNegative, source, maximumFractionDigits, true); + } + + /** + * Set the digit list to a representation of the given double value. + * This method supports both fixed-point and exponential notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be Inf, -Inf, Nan, + * or a value <= 0. + * @param maximumDigits The most fractional or total digits which should + * be converted. + * @param fixedPoint If true, then maximumDigits is the maximum + * fractional digits to be converted. If false, total digits. + */ + final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) { + set(isNegative, Double.toString(source), maximumDigits, fixedPoint); + } + + /** + * Generate a representation of the form DDDDD, DDDDD.DDDDD, or + * DDDDDE+/-DDDDD. + */ + final void set(boolean isNegative, String s, int maximumDigits, boolean fixedPoint) { + this.isNegative = isNegative; + int len = s.length(); + char[] source = getDataChars(len); + s.getChars(0, len, source, 0); + + decimalAt = -1; + count = 0; + int exponent = 0; + // Number of zeros between decimal point and first non-zero digit after + // decimal point, for numbers < 1. + int leadingZerosAfterDecimal = 0; + boolean nonZeroDigitSeen = false; + + for (int i = 0; i < len; ) { + char c = source[i++]; + if (c == '.') { + decimalAt = count; + } else if (c == 'e' || c == 'E') { + exponent = parseInt(source, i, len); + break; + } else { + if (!nonZeroDigitSeen) { + nonZeroDigitSeen = (c != '0'); + if (!nonZeroDigitSeen && decimalAt != -1) + ++leadingZerosAfterDecimal; + } + if (nonZeroDigitSeen) { + digits[count++] = c; + } + } + } + if (decimalAt == -1) { + decimalAt = count; + } + if (nonZeroDigitSeen) { + decimalAt += exponent - leadingZerosAfterDecimal; + } + + if (fixedPoint) { + // The negative of the exponent represents the number of leading + // zeros between the decimal and the first non-zero digit, for + // a value < 0.1 (e.g., for 0.00123, -decimalAt == 2). If this + // is more than the maximum fraction digits, then we have an underflow + // for the printed representation. + if (-decimalAt > maximumDigits) { + // Handle an underflow to zero when we round something like + // 0.0009 to 2 fractional digits. + count = 0; + return; + } else if (-decimalAt == maximumDigits) { + // If we round 0.0009 to 3 fractional digits, then we have to + // create a new one digit in the least significant location. + if (shouldRoundUp(0)) { + count = 1; + ++decimalAt; + digits[0] = '1'; + } else { + count = 0; + } + return; + } + // else fall through + } + + // Eliminate trailing zeros. + while (count > 1 && digits[count - 1] == '0') { + --count; + } + + // Eliminate digits beyond maximum digits to be displayed. + // Round up if appropriate. + round(fixedPoint ? (maximumDigits + decimalAt) : maximumDigits); + } + + /** + * Round the representation to the given number of digits. + * @param maximumDigits The maximum number of digits to be shown. + * Upon return, count will be less than or equal to maximumDigits. + */ + private final void round(int maximumDigits) { + // Eliminate digits beyond maximum digits to be displayed. + // Round up if appropriate. + if (maximumDigits >= 0 && maximumDigits < count) { + if (shouldRoundUp(maximumDigits)) { + // Rounding up involved incrementing digits from LSD to MSD. + // In most cases this is simple, but in a worst case situation + // (9999..99) we have to adjust the decimalAt value. + for (;;) { + --maximumDigits; + if (maximumDigits < 0) { + // We have all 9's, so we increment to a single digit + // of one and adjust the exponent. + digits[0] = '1'; + ++decimalAt; + maximumDigits = 0; // Adjust the count + break; + } + + ++digits[maximumDigits]; + if (digits[maximumDigits] <= '9') break; + // digits[maximumDigits] = '0'; // Unnecessary since we'll truncate this + } + ++maximumDigits; // Increment for use as count + } + count = maximumDigits; + + // Eliminate trailing zeros. + while (count > 1 && digits[count-1] == '0') { + --count; + } + } + } + + + /** + * Return true if truncating the representation to the given number + * of digits will result in an increment to the last digit. This + * method implements the rounding modes defined in the + * java.math.RoundingMode class. + * [bnf] + * @param maximumDigits the number of digits to keep, from 0 to + * count-1. If 0, then all digits are rounded away, and + * this method returns true if a one should be generated (e.g., formatting + * 0.09 with "#.#"). + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return true if digit maximumDigits-1 should be + * incremented + */ + private boolean shouldRoundUp(int maximumDigits) { + if (maximumDigits < count) { + switch(roundingMode) { + case UP: + for (int i=maximumDigits; i= '5') { + return true; + } + break; + case HALF_DOWN: + if (digits[maximumDigits] > '5') { + return true; + } else if (digits[maximumDigits] == '5' ) { + for (int i=maximumDigits+1; i '5') { + return true; + } else if (digits[maximumDigits] == '5' ) { + for (int i=maximumDigits+1; i 0 && (digits[maximumDigits-1] % 2 != 0); + } + break; + case UNNECESSARY: + for (int i=maximumDigits; i= 0 or == + * Long.MIN_VALUE. + * @param maximumDigits The most digits which should be converted. + * If maximumDigits is lower than the number of significant digits + * in source, the representation will be rounded. Ignored if <= 0. + */ + public final void set(boolean isNegative, long source, int maximumDigits) { + this.isNegative = isNegative; + + // This method does not expect a negative number. However, + // "source" can be a Long.MIN_VALUE (-9223372036854775808), + // if the number being formatted is a Long.MIN_VALUE. In that + // case, it will be formatted as -Long.MIN_VALUE, a number + // which is outside the legal range of a long, but which can + // be represented by DigitList. + if (source <= 0) { + if (source == Long.MIN_VALUE) { + decimalAt = count = MAX_COUNT; + System.arraycopy(LONG_MIN_REP, 0, digits, 0, count); + } else { + decimalAt = count = 0; // Values <= 0 format as zero + } + } else { + // Rewritten to improve performance. I used to call + // Long.toString(), which was about 4x slower than this code. + int left = MAX_COUNT; + int right; + while (source > 0) { + digits[--left] = (char)('0' + (source % 10)); + source /= 10; + } + decimalAt = MAX_COUNT - left; + // Don't copy trailing zeros. We are guaranteed that there is at + // least one non-zero digit, so we don't have to check lower bounds. + for (right = MAX_COUNT - 1; digits[right] == '0'; --right) + ; + count = right - left + 1; + System.arraycopy(digits, left, digits, 0, count); + } + if (maximumDigits > 0) round(maximumDigits); + } + + /** + * Set the digit list to a representation of the given BigDecimal value. + * This method supports both fixed-point and exponential notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be a value <= 0. + * @param maximumDigits The most fractional or total digits which should + * be converted. + * @param fixedPoint If true, then maximumDigits is the maximum + * fractional digits to be converted. If false, total digits. + */ + final void set(boolean isNegative, BigDecimal source, int maximumDigits, boolean fixedPoint) { + String s = source.toString(); + extendDigits(s.length()); + + set(isNegative, s, maximumDigits, fixedPoint); + } + + /** + * Set the digit list to a representation of the given BigInteger value. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must be >= 0. + * @param maximumDigits The most digits which should be converted. + * If maximumDigits is lower than the number of significant digits + * in source, the representation will be rounded. Ignored if <= 0. + */ + final void set(boolean isNegative, BigInteger source, int maximumDigits) { + this.isNegative = isNegative; + String s = source.toString(); + int len = s.length(); + extendDigits(len); + s.getChars(0, len, digits, 0); + + decimalAt = len; + int right; + for (right = len - 1; right >= 0 && digits[right] == '0'; --right) + ; + count = right + 1; + + if (maximumDigits > 0) { + round(maximumDigits); + } + } + + /** + * equality test between two digit lists. + */ + public boolean equals(Object obj) { + if (this == obj) // quick check + return true; + if (!(obj instanceof DigitList)) // (1) same object? + return false; + DigitList other = (DigitList) obj; + if (count != other.count || + decimalAt != other.decimalAt) + return false; + for (int i = 0; i < count; i++) + if (digits[i] != other.digits[i]) + return false; + return true; + } + + /** + * Generates the hash code for the digit list. + */ + public int hashCode() { + int hashcode = decimalAt; + + for (int i = 0; i < count; i++) { + hashcode = hashcode * 37 + digits[i]; + } + + return hashcode; + } + + /** + * Creates a copy of this object. + * @return a clone of this instance. + */ + public Object clone() { + try { + DigitList other = (DigitList) super.clone(); + char[] newDigits = new char[digits.length]; + System.arraycopy(digits, 0, newDigits, 0, digits.length); + other.digits = newDigits; + other.tempBuffer = null; + return other; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Returns true if this DigitList represents Long.MIN_VALUE; + * false, otherwise. This is required so that getLong() works. + */ + private boolean isLongMIN_VALUE() { + if (decimalAt != count || count != MAX_COUNT) { + return false; + } + + for (int i = 0; i < count; ++i) { + if (digits[i] != LONG_MIN_REP[i]) return false; + } + + return true; + } + + private static final int parseInt(char[] str, int offset, int strLen) { + char c; + boolean positive = true; + if ((c = str[offset]) == '-') { + positive = false; + offset++; + } else if (c == '+') { + offset++; + } + + int value = 0; + while (offset < strLen) { + c = str[offset++]; + if (c >= '0' && c <= '9') { + value = value * 10 + (c - '0'); + } else { + break; + } + } + return positive ? value : -value; + } + + // The digit part of -9223372036854775808L + private static final char[] LONG_MIN_REP = "9223372036854775808".toCharArray(); + + public String toString() { + if (isZero()) { + return "0"; + } + StringBuffer buf = getStringBuffer(); + buf.append("0."); + buf.append(digits, 0, count); + buf.append("x10^"); + buf.append(decimalAt); + return buf.toString(); + } + + private StringBuffer tempBuffer; + + private StringBuffer getStringBuffer() { + if (tempBuffer == null) { + tempBuffer = new StringBuffer(MAX_COUNT); + } else { + tempBuffer.setLength(0); + } + return tempBuffer; + } + + private void extendDigits(int len) { + if (len > digits.length) { + digits = new char[len]; + } + } + + private final char[] getDataChars(int length) { + if (data == null || data.length < length) { + data = new char[length]; + } + return data; + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/DontCareFieldPosition.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/DontCareFieldPosition.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2002, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.text; + +/** + * DontCareFieldPosition defines no-op FieldDelegate. Its + * singleton is used for the format methods that don't take a + * FieldPosition. + */ +class DontCareFieldPosition extends FieldPosition { + // The singleton of DontCareFieldPosition. + static final FieldPosition INSTANCE = new DontCareFieldPosition(); + + private final Format.FieldDelegate noDelegate = new Format.FieldDelegate() { + public void formatted(Format.Field attr, Object value, int start, + int end, StringBuffer buffer) { + } + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer) { + } + }; + + private DontCareFieldPosition() { + super(0); + } + + Format.FieldDelegate getFieldDelegate() { + return noDelegate; + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/FieldPosition.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/FieldPosition.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,303 @@ +/* + * Copyright (c) 1996, 2002, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +/** + * FieldPosition is a simple class used by Format + * and its subclasses to identify fields in formatted output. Fields can + * be identified in two ways: + *
    + *
  • By an integer constant, whose names typically end with + * _FIELD. The constants are defined in the various + * subclasses of Format. + *
  • By a Format.Field constant, see ERA_FIELD + * and its friends in DateFormat for an example. + *
+ *

+ * FieldPosition keeps track of the position of the + * field within the formatted output with two indices: the index + * of the first character of the field and the index of the last + * character of the field. + * + *

+ * One version of the format method in the various + * Format classes requires a FieldPosition + * object as an argument. You use this format method + * to perform partial formatting or to get information about the + * formatted output (such as the position of a field). + * + *

+ * If you are interested in the positions of all attributes in the + * formatted string use the Format method + * formatToCharacterIterator. + * + * @author Mark Davis + * @see java.text.Format + */ +public class FieldPosition { + + /** + * Input: Desired field to determine start and end offsets for. + * The meaning depends on the subclass of Format. + */ + int field = 0; + + /** + * Output: End offset of field in text. + * If the field does not occur in the text, 0 is returned. + */ + int endIndex = 0; + + /** + * Output: Start offset of field in text. + * If the field does not occur in the text, 0 is returned. + */ + int beginIndex = 0; + + /** + * Desired field this FieldPosition is for. + */ + private Format.Field attribute; + + /** + * Creates a FieldPosition object for the given field. Fields are + * identified by constants, whose names typically end with _FIELD, + * in the various subclasses of Format. + * + * @see java.text.NumberFormat#INTEGER_FIELD + * @see java.text.NumberFormat#FRACTION_FIELD + * @see java.text.DateFormat#YEAR_FIELD + * @see java.text.DateFormat#MONTH_FIELD + */ + public FieldPosition(int field) { + this.field = field; + } + + /** + * Creates a FieldPosition object for the given field constant. Fields are + * identified by constants defined in the various Format + * subclasses. This is equivalent to calling + * new FieldPosition(attribute, -1). + * + * @param attribute Format.Field constant identifying a field + * @since 1.4 + */ + public FieldPosition(Format.Field attribute) { + this(attribute, -1); + } + + /** + * Creates a FieldPosition object for the given field. + * The field is identified by an attribute constant from one of the + * Field subclasses as well as an integer field ID + * defined by the Format subclasses. Format + * subclasses that are aware of Field should give precedence + * to attribute and ignore fieldID if + * attribute is not null. However, older Format + * subclasses may not be aware of Field and rely on + * fieldID. If the field has no corresponding integer + * constant, fieldID should be -1. + * + * @param attribute Format.Field constant identifying a field + * @param fieldID integer constantce identifying a field + * @since 1.4 + */ + public FieldPosition(Format.Field attribute, int fieldID) { + this.attribute = attribute; + this.field = fieldID; + } + + /** + * Returns the field identifier as an attribute constant + * from one of the Field subclasses. May return null if + * the field is specified only by an integer field ID. + * + * @return Identifier for the field + * @since 1.4 + */ + public Format.Field getFieldAttribute() { + return attribute; + } + + /** + * Retrieves the field identifier. + */ + public int getField() { + return field; + } + + /** + * Retrieves the index of the first character in the requested field. + */ + public int getBeginIndex() { + return beginIndex; + } + + /** + * Retrieves the index of the character following the last character in the + * requested field. + */ + public int getEndIndex() { + return endIndex; + } + + /** + * Sets the begin index. For use by subclasses of Format. + * @since 1.2 + */ + public void setBeginIndex(int bi) { + beginIndex = bi; + } + + /** + * Sets the end index. For use by subclasses of Format. + * @since 1.2 + */ + public void setEndIndex(int ei) { + endIndex = ei; + } + + /** + * Returns a Format.FieldDelegate instance that is associated + * with the FieldPosition. When the delegate is notified of the same + * field the FieldPosition is associated with, the begin/end will be + * adjusted. + */ + Format.FieldDelegate getFieldDelegate() { + return new Delegate(); + } + + /** + * Overrides equals + */ + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof FieldPosition)) + return false; + FieldPosition other = (FieldPosition) obj; + if (attribute == null) { + if (other.attribute != null) { + return false; + } + } + else if (!attribute.equals(other.attribute)) { + return false; + } + return (beginIndex == other.beginIndex + && endIndex == other.endIndex + && field == other.field); + } + + /** + * Returns a hash code for this FieldPosition. + * @return a hash code value for this object + */ + public int hashCode() { + return (field << 24) | (beginIndex << 16) | endIndex; + } + + /** + * Return a string representation of this FieldPosition. + * @return a string representation of this object + */ + public String toString() { + return getClass().getName() + + "[field=" + field + ",attribute=" + attribute + + ",beginIndex=" + beginIndex + + ",endIndex=" + endIndex + ']'; + } + + + /** + * Return true if the receiver wants a Format.Field value and + * attribute is equal to it. + */ + private boolean matchesField(Format.Field attribute) { + if (this.attribute != null) { + return this.attribute.equals(attribute); + } + return false; + } + + /** + * Return true if the receiver wants a Format.Field value and + * attribute is equal to it, or true if the receiver + * represents an inteter constant and field equals it. + */ + private boolean matchesField(Format.Field attribute, int field) { + if (this.attribute != null) { + return this.attribute.equals(attribute); + } + return (field == this.field); + } + + + /** + * An implementation of FieldDelegate that will adjust the begin/end + * of the FieldPosition if the arguments match the field of + * the FieldPosition. + */ + private class Delegate implements Format.FieldDelegate { + /** + * Indicates whether the field has been encountered before. If this + * is true, and formatted is invoked, the begin/end + * are not updated. + */ + private boolean encounteredField; + + public void formatted(Format.Field attr, Object value, int start, + int end, StringBuffer buffer) { + if (!encounteredField && matchesField(attr)) { + setBeginIndex(start); + setEndIndex(end); + encounteredField = (start != end); + } + } + + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer) { + if (!encounteredField && matchesField(attr, fieldID)) { + setBeginIndex(start); + setEndIndex(end); + encounteredField = (start != end); + } + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/Format.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/Format.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,406 @@ +/* + * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.Serializable; + +/** + * Format is an abstract base class for formatting locale-sensitive + * information such as dates, messages, and numbers. + * + *

+ * Format defines the programming interface for formatting + * locale-sensitive objects into Strings (the + * format method) and for parsing Strings back + * into objects (the parseObject method). + * + *

+ * Generally, a format's parseObject method must be able to parse + * any string formatted by its format method. However, there may + * be exceptional cases where this is not possible. For example, a + * format method might create two adjacent integer numbers with + * no separator in between, and in this case the parseObject could + * not tell which digits belong to which number. + * + *

Subclassing

+ * + *

+ * The Java Platform provides three specialized subclasses of Format-- + * DateFormat, MessageFormat, and + * NumberFormat--for formatting dates, messages, and numbers, + * respectively. + *

+ * Concrete subclasses must implement three methods: + *

    + *
  1. format(Object obj, StringBuffer toAppendTo, FieldPosition pos) + *
  2. formatToCharacterIterator(Object obj) + *
  3. parseObject(String source, ParsePosition pos) + *
+ * These general methods allow polymorphic parsing and formatting of objects + * and are used, for example, by MessageFormat. + * Subclasses often also provide additional format methods for + * specific input types as well as parse methods for specific + * result types. Any parse method that does not take a + * ParsePosition argument should throw ParseException + * when no text in the required format is at the beginning of the input text. + * + *

+ * Most subclasses will also implement the following factory methods: + *

    + *
  1. + * getInstance for getting a useful format object appropriate + * for the current locale + *
  2. + * getInstance(Locale) for getting a useful format + * object appropriate for the specified locale + *
+ * In addition, some subclasses may also implement other + * getXxxxInstance methods for more specialized control. For + * example, the NumberFormat class provides + * getPercentInstance and getCurrencyInstance + * methods for getting specialized number formatters. + * + *

+ * Subclasses of Format that allow programmers to create objects + * for locales (with getInstance(Locale) for example) + * must also implement the following class method: + *

+ *
+ * public static Locale[] getAvailableLocales()
+ * 
+ *
+ * + *

+ * And finally subclasses may define a set of constants to identify the various + * fields in the formatted output. These constants are used to create a FieldPosition + * object which identifies what information is contained in the field and its + * position in the formatted result. These constants should be named + * item_FIELD where item identifies + * the field. For examples of these constants, see ERA_FIELD and its + * friends in {@link DateFormat}. + * + *

Synchronization

+ * + *

+ * Formats are generally not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see java.text.ParsePosition + * @see java.text.FieldPosition + * @see java.text.NumberFormat + * @see java.text.DateFormat + * @see java.text.MessageFormat + * @author Mark Davis + */ +public abstract class Format implements Serializable, Cloneable { + + private static final long serialVersionUID = -299282585814624189L; + + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + protected Format() { + } + + /** + * Formats an object to produce a string. This is equivalent to + *

+ * {@link #format(Object, StringBuffer, FieldPosition) format}(obj, + * new StringBuffer(), new FieldPosition(0)).toString(); + *
+ * + * @param obj The object to format + * @return Formatted string. + * @exception IllegalArgumentException if the Format cannot format the given + * object + */ + public final String format (Object obj) { + return format(obj, new StringBuffer(), new FieldPosition(0)).toString(); + } + + /** + * Formats an object and appends the resulting text to a given string + * buffer. + * If the pos argument identifies a field used by the format, + * then its indices are set to the beginning and end of the first such + * field encountered. + * + * @param obj The object to format + * @param toAppendTo where the text is to be appended + * @param pos A FieldPosition identifying a field + * in the formatted text + * @return the string buffer passed in as toAppendTo, + * with formatted text appended + * @exception NullPointerException if toAppendTo or + * pos is null + * @exception IllegalArgumentException if the Format cannot format the given + * object + */ + public abstract StringBuffer format(Object obj, + StringBuffer toAppendTo, + FieldPosition pos); + + /** + * Formats an Object producing an AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * Each attribute key of the AttributedCharacterIterator will be of type + * Field. It is up to each Format implementation + * to define what the legal values are for each attribute in the + * AttributedCharacterIterator, but typically the attribute + * key is also used as the attribute value. + *

The default implementation creates an + * AttributedCharacterIterator with no attributes. Subclasses + * that support fields should override this and create an + * AttributedCharacterIterator with meaningful attributes. + * + * @exception NullPointerException if obj is null. + * @exception IllegalArgumentException when the Format cannot format the + * given object. + * @param obj The object to format + * @return AttributedCharacterIterator describing the formatted value. + * @since 1.4 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object obj) { + return createAttributedCharacterIterator(format(obj)); + } + + /** + * Parses text from a string to produce an object. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * object is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + * + * @param source A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return An Object parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if pos is null. + */ + public abstract Object parseObject (String source, ParsePosition pos); + + /** + * Parses text from the beginning of the given string to produce an object. + * The method may not use the entire text of the given string. + * + * @param source A String whose beginning should be parsed. + * @return An Object parsed from the string. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public Object parseObject(String source) throws ParseException { + ParsePosition pos = new ParsePosition(0); + Object result = parseObject(source, pos); + if (pos.index == 0) { + throw new ParseException("Format.parseObject(String) failed", + pos.errorIndex); + } + return result; + } + + /** + * Creates and returns a copy of this object. + * + * @return a clone of this instance. + */ + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + // will never happen + return null; + } + } + + // + // Convenience methods for creating AttributedCharacterIterators from + // different parameters. + // + + /** + * Creates an AttributedCharacterIterator for the String + * s. + * + * @param s String to create AttributedCharacterIterator from + * @return AttributedCharacterIterator wrapping s + */ + AttributedCharacterIterator createAttributedCharacterIterator(String s) { + AttributedString as = new AttributedString(s); + + return as.getIterator(); + } + + /** + * Creates an AttributedCharacterIterator containg the + * concatenated contents of the passed in + * AttributedCharacterIterators. + * + * @param iterators AttributedCharacterIterators used to create resulting + * AttributedCharacterIterators + * @return AttributedCharacterIterator wrapping passed in + * AttributedCharacterIterators + */ + AttributedCharacterIterator createAttributedCharacterIterator( + AttributedCharacterIterator[] iterators) { + AttributedString as = new AttributedString(iterators); + + return as.getIterator(); + } + + /** + * Returns an AttributedCharacterIterator with the String + * string and additional key/value pair key, + * value. + * + * @param string String to create AttributedCharacterIterator from + * @param key Key for AttributedCharacterIterator + * @param value Value associated with key in AttributedCharacterIterator + * @return AttributedCharacterIterator wrapping args + */ + AttributedCharacterIterator createAttributedCharacterIterator( + String string, AttributedCharacterIterator.Attribute key, + Object value) { + AttributedString as = new AttributedString(string); + + as.addAttribute(key, value); + return as.getIterator(); + } + + /** + * Creates an AttributedCharacterIterator with the contents of + * iterator and the additional attribute key + * value. + * + * @param iterator Initial AttributedCharacterIterator to add arg to + * @param key Key for AttributedCharacterIterator + * @param value Value associated with key in AttributedCharacterIterator + * @return AttributedCharacterIterator wrapping args + */ + AttributedCharacterIterator createAttributedCharacterIterator( + AttributedCharacterIterator iterator, + AttributedCharacterIterator.Attribute key, Object value) { + AttributedString as = new AttributedString(iterator); + + as.addAttribute(key, value); + return as.getIterator(); + } + + + /** + * Defines constants that are used as attribute keys in the + * AttributedCharacterIterator returned + * from Format.formatToCharacterIterator and as + * field identifiers in FieldPosition. + * + * @since 1.4 + */ + public static class Field extends AttributedCharacterIterator.Attribute { + + // Proclaim serial compatibility with 1.4 FCS + private static final long serialVersionUID = 276966692217360283L; + + /** + * Creates a Field with the specified name. + * + * @param name Name of the attribute + */ + protected Field(String name) { + super(name); + } + } + + + /** + * FieldDelegate is notified by the various Format + * implementations as they are formatting the Objects. This allows for + * storage of the individual sections of the formatted String for + * later use, such as in a FieldPosition or for an + * AttributedCharacterIterator. + *

+ * Delegates should NOT assume that the Format will notify + * the delegate of fields in any particular order. + * + * @see FieldPosition.Delegate + * @see CharacterIteratorFieldDelegate + */ + interface FieldDelegate { + /** + * Notified when a particular region of the String is formatted. This + * method will be invoked if there is no corresponding integer field id + * matching attr. + * + * @param attr Identifies the field matched + * @param value Value associated with the field + * @param start Beginning location of the field, will be >= 0 + * @param end End of the field, will be >= start and <= buffer.length() + * @param buffer Contains current formatted value, receiver should + * NOT modify it. + */ + public void formatted(Format.Field attr, Object value, int start, + int end, StringBuffer buffer); + + /** + * Notified when a particular region of the String is formatted. + * + * @param fieldID Identifies the field by integer + * @param attr Identifies the field matched + * @param value Value associated with the field + * @param start Beginning location of the field, will be >= 0 + * @param end End of the field, will be >= start and <= buffer.length() + * @param buffer Contains current formatted value, receiver should + * NOT modify it. + */ + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer); + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/MessageFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/MessageFormat.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,1594 @@ +/* + * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; + + +/** + * MessageFormat provides a means to produce concatenated + * messages in a language-neutral way. Use this to construct messages + * displayed for end users. + * + *

+ * MessageFormat takes a set of objects, formats them, then + * inserts the formatted strings into the pattern at the appropriate places. + * + *

+ * Note: + * MessageFormat differs from the other Format + * classes in that you create a MessageFormat object with one + * of its constructors (not with a getInstance style factory + * method). The factory methods aren't necessary because MessageFormat + * itself doesn't implement locale specific behavior. Any locale specific + * behavior is defined by the pattern that you provide as well as the + * subformats used for inserted arguments. + * + *

Patterns and Their Interpretation

+ * + * MessageFormat uses patterns of the following form: + *
+ * MessageFormatPattern:
+ *         String
+ *         MessageFormatPattern FormatElement String
+ *
+ * FormatElement:
+ *         { ArgumentIndex }
+ *         { ArgumentIndex , FormatType }
+ *         { ArgumentIndex , FormatType , FormatStyle }
+ *
+ * FormatType: one of 
+ *         number date time choice
+ *
+ * FormatStyle:
+ *         short
+ *         medium
+ *         long
+ *         full
+ *         integer
+ *         currency
+ *         percent
+ *         SubformatPattern
+ * 
+ * + *

Within a String, a pair of single quotes can be used to + * quote any arbitrary characters except single quotes. For example, + * pattern string "'{0}'" represents string + * "{0}", not a FormatElement. A single quote itself + * must be represented by doubled single quotes {@code ''} throughout a + * String. For example, pattern string "'{''}'" is + * interpreted as a sequence of '{ (start of quoting and a + * left curly brace), '' (a single quote), and + * }' (a right curly brace and end of quoting), + * not '{' and '}' (quoted left and + * right curly braces): representing string "{'}", + * not "{}". + * + *

A SubformatPattern is interpreted by its corresponding + * subformat, and subformat-dependent pattern rules apply. For example, + * pattern string "{1,number,$'#',##}" + * (SubformatPattern with underline) will produce a number format + * with the pound-sign quoted, with a result such as: {@code + * "$#31,45"}. Refer to each {@code Format} subclass documentation for + * details. + * + *

Any unmatched quote is treated as closed at the end of the given + * pattern. For example, pattern string {@code "'{0}"} is treated as + * pattern {@code "'{0}'"}. + * + *

Any curly braces within an unquoted pattern must be balanced. For + * example, "ab {0} de" and "ab '}' de" are + * valid patterns, but "ab {0'}' de", "ab } de" + * and "''{''" are not. + * + *

+ *

Warning:
The rules for using quotes within message + * format patterns unfortunately have shown to be somewhat confusing. + * In particular, it isn't always obvious to localizers whether single + * quotes need to be doubled or not. Make sure to inform localizers about + * the rules, and tell them (for example, by using comments in resource + * bundle source files) which strings will be processed by {@code MessageFormat}. + * Note that localizers may need to use single quotes in translated + * strings where the original version doesn't have them. + *
+ *

+ * The ArgumentIndex value is a non-negative integer written + * using the digits {@code '0'} through {@code '9'}, and represents an index into the + * {@code arguments} array passed to the {@code format} methods + * or the result array returned by the {@code parse} methods. + *

+ * The FormatType and FormatStyle values are used to create + * a {@code Format} instance for the format element. The following + * table shows how the values map to {@code Format} instances. Combinations not + * shown in the table are illegal. A SubformatPattern must + * be a valid pattern string for the {@code Format} subclass used. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
FormatType + * FormatStyle + * Subformat Created + *
(none) + * (none) + * null + *
number + * (none) + * {@link NumberFormat#getInstance(Locale) NumberFormat.getInstance}{@code (getLocale())} + *
integer + * {@link NumberFormat#getIntegerInstance(Locale) NumberFormat.getIntegerInstance}{@code (getLocale())} + *
currency + * {@link NumberFormat#getCurrencyInstance(Locale) NumberFormat.getCurrencyInstance}{@code (getLocale())} + *
percent + * {@link NumberFormat#getPercentInstance(Locale) NumberFormat.getPercentInstance}{@code (getLocale())} + *
SubformatPattern + * {@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) DecimalFormatSymbols.getInstance}{@code (getLocale()))} + *
date + * (none) + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + *
short + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} + *
medium + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + *
long + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} + *
full + * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} + *
SubformatPattern + * {@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())} + *
time + * (none) + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + *
short + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} + *
medium + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + *
long + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} + *
full + * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} + *
SubformatPattern + * {@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())} + *
choice + * SubformatPattern + * {@code new} {@link ChoiceFormat#ChoiceFormat(String) ChoiceFormat}{@code (subformatPattern)} + *
+ *

+ * + *

Usage Information

+ * + *

+ * Here are some examples of usage. + * In real internationalized programs, the message format pattern and other + * static strings will, of course, be obtained from resource bundles. + * Other parameters will be dynamically determined at runtime. + *

+ * The first example uses the static method MessageFormat.format, + * which internally creates a MessageFormat for one-time use: + *

+ * int planet = 7;
+ * String event = "a disturbance in the Force";
+ *
+ * String result = MessageFormat.format(
+ *     "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
+ *     planet, new Date(), event);
+ * 
+ * The output is: + *
+ * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
+ * 
+ * + *

+ * The following example creates a MessageFormat instance that + * can be used repeatedly: + *

+ * int fileCount = 1273;
+ * String diskName = "MyDisk";
+ * Object[] testArgs = {new Long(fileCount), diskName};
+ *
+ * MessageFormat form = new MessageFormat(
+ *     "The disk \"{1}\" contains {0} file(s).");
+ *
+ * System.out.println(form.format(testArgs));
+ * 
+ * The output with different values for fileCount: + *
+ * The disk "MyDisk" contains 0 file(s).
+ * The disk "MyDisk" contains 1 file(s).
+ * The disk "MyDisk" contains 1,273 file(s).
+ * 
+ * + *

+ * For more sophisticated patterns, you can use a ChoiceFormat + * to produce correct forms for singular and plural: + *

+ * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
+ * double[] filelimits = {0,1,2};
+ * String[] filepart = {"no files","one file","{0,number} files"};
+ * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
+ * form.setFormatByArgumentIndex(0, fileform);
+ *
+ * int fileCount = 1273;
+ * String diskName = "MyDisk";
+ * Object[] testArgs = {new Long(fileCount), diskName};
+ *
+ * System.out.println(form.format(testArgs));
+ * 
+ * The output with different values for fileCount: + *
+ * The disk "MyDisk" contains no files.
+ * The disk "MyDisk" contains one file.
+ * The disk "MyDisk" contains 1,273 files.
+ * 
+ * + *

+ * You can create the ChoiceFormat programmatically, as in the + * above example, or by using a pattern. See {@link ChoiceFormat} + * for more information. + *

+ * form.applyPattern(
+ *    "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");
+ * 
+ * + *

+ * Note: As we see above, the string produced + * by a ChoiceFormat in MessageFormat is treated as special; + * occurrences of '{' are used to indicate subformats, and cause recursion. + * If you create both a MessageFormat and ChoiceFormat + * programmatically (instead of using the string patterns), then be careful not to + * produce a format that recurses on itself, which will cause an infinite loop. + *

+ * When a single argument is parsed more than once in the string, the last match + * will be the final result of the parsing. For example, + *

+ * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
+ * Object[] objs = {new Double(3.1415)};
+ * String result = mf.format( objs );
+ * // result now equals "3.14, 3.1"
+ * objs = null;
+ * objs = mf.parse(result, new ParsePosition(0));
+ * // objs now equals {new Double(3.1)}
+ * 
+ * + *

+ * Likewise, parsing with a {@code MessageFormat} object using patterns containing + * multiple occurrences of the same argument would return the last match. For + * example, + *

+ * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
+ * String forParsing = "x, y, z";
+ * Object[] objs = mf.parse(forParsing, new ParsePosition(0));
+ * // result now equals {new String("z")}
+ * 
+ * + *

Synchronization

+ * + *

+ * Message formats are not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see java.util.Locale + * @see Format + * @see NumberFormat + * @see DecimalFormat + * @see DecimalFormatSymbols + * @see ChoiceFormat + * @see DateFormat + * @see SimpleDateFormat + * + * @author Mark Davis + */ + +public class MessageFormat extends Format { + + private static final long serialVersionUID = 6479157306784022952L; + + /** + * Constructs a MessageFormat for the default locale and the + * specified pattern. + * The constructor first sets the locale, then parses the pattern and + * creates a list of subformats for the format elements contained in it. + * Patterns and their interpretation are specified in the + * class description. + * + * @param pattern the pattern for this message format + * @exception IllegalArgumentException if the pattern is invalid + */ + public MessageFormat(String pattern) { + this.locale = Locale.getDefault(Locale.Category.FORMAT); + applyPattern(pattern); + } + + /** + * Constructs a MessageFormat for the specified locale and + * pattern. + * The constructor first sets the locale, then parses the pattern and + * creates a list of subformats for the format elements contained in it. + * Patterns and their interpretation are specified in the + * class description. + * + * @param pattern the pattern for this message format + * @param locale the locale for this message format + * @exception IllegalArgumentException if the pattern is invalid + * @since 1.4 + */ + public MessageFormat(String pattern, Locale locale) { + this.locale = locale; + applyPattern(pattern); + } + + /** + * Sets the locale to be used when creating or comparing subformats. + * This affects subsequent calls + *

    + *
  • to the {@link #applyPattern applyPattern} + * and {@link #toPattern toPattern} methods if format elements specify + * a format type and therefore have the subformats created in the + * applyPattern method, as well as + *
  • to the format and + * {@link #formatToCharacterIterator formatToCharacterIterator} methods + * if format elements do not specify a format type and therefore have + * the subformats created in the formatting methods. + *
+ * Subformats that have already been created are not affected. + * + * @param locale the locale to be used when creating or comparing subformats + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * Gets the locale that's used when creating or comparing subformats. + * + * @return the locale used when creating or comparing subformats + */ + public Locale getLocale() { + return locale; + } + + + /** + * Sets the pattern used by this message format. + * The method parses the pattern and creates a list of subformats + * for the format elements contained in it. + * Patterns and their interpretation are specified in the + * class description. + * + * @param pattern the pattern for this message format + * @exception IllegalArgumentException if the pattern is invalid + */ + public void applyPattern(String pattern) { + StringBuilder[] segments = new StringBuilder[4]; + // Allocate only segments[SEG_RAW] here. The rest are + // allocated on demand. + segments[SEG_RAW] = new StringBuilder(); + + int part = SEG_RAW; + int formatNumber = 0; + boolean inQuote = false; + int braceStack = 0; + maxOffset = -1; + for (int i = 0; i < pattern.length(); ++i) { + char ch = pattern.charAt(i); + if (part == SEG_RAW) { + if (ch == '\'') { + if (i + 1 < pattern.length() + && pattern.charAt(i+1) == '\'') { + segments[part].append(ch); // handle doubles + ++i; + } else { + inQuote = !inQuote; + } + } else if (ch == '{' && !inQuote) { + part = SEG_INDEX; + if (segments[SEG_INDEX] == null) { + segments[SEG_INDEX] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + } else { + if (inQuote) { // just copy quotes in parts + segments[part].append(ch); + if (ch == '\'') { + inQuote = false; + } + } else { + switch (ch) { + case ',': + if (part < SEG_MODIFIER) { + if (segments[++part] == null) { + segments[part] = new StringBuilder(); + } + } else { + segments[part].append(ch); + } + break; + case '{': + ++braceStack; + segments[part].append(ch); + break; + case '}': + if (braceStack == 0) { + part = SEG_RAW; + makeFormat(i, formatNumber, segments); + formatNumber++; + // throw away other segments + segments[SEG_INDEX] = null; + segments[SEG_TYPE] = null; + segments[SEG_MODIFIER] = null; + } else { + --braceStack; + segments[part].append(ch); + } + break; + case ' ': + // Skip any leading space chars for SEG_TYPE. + if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) { + segments[part].append(ch); + } + break; + case '\'': + inQuote = true; + // fall through, so we keep quotes in other parts + default: + segments[part].append(ch); + break; + } + } + } + } + if (braceStack == 0 && part != 0) { + maxOffset = -1; + throw new IllegalArgumentException("Unmatched braces in the pattern."); + } + this.pattern = segments[0].toString(); + } + + + /** + * Returns a pattern representing the current state of the message format. + * The string is constructed from internal information and therefore + * does not necessarily equal the previously applied pattern. + * + * @return a pattern representing the current state of the message format + */ + public String toPattern() { + // later, make this more extensible + int lastOffset = 0; + StringBuilder result = new StringBuilder(); + for (int i = 0; i <= maxOffset; ++i) { + copyAndFixQuotes(pattern, lastOffset, offsets[i], result); + lastOffset = offsets[i]; + result.append('{').append(argumentNumbers[i]); + Format fmt = formats[i]; + if (fmt == null) { + // do nothing, string format + } else if (fmt instanceof NumberFormat) { + if (fmt.equals(NumberFormat.getInstance(locale))) { + result.append(",number"); + } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) { + result.append(",number,currency"); + } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) { + result.append(",number,percent"); + } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) { + result.append(",number,integer"); + } else { + if (fmt instanceof DecimalFormat) { + result.append(",number,").append(((DecimalFormat)fmt).toPattern()); + } else if (fmt instanceof ChoiceFormat) { + result.append(",choice,").append(((ChoiceFormat)fmt).toPattern()); + } else { + // UNKNOWN + } + } + } else if (fmt instanceof DateFormat) { + int index; + for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) { + DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index], + locale); + if (fmt.equals(df)) { + result.append(",date"); + break; + } + df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index], + locale); + if (fmt.equals(df)) { + result.append(",time"); + break; + } + } + if (index >= DATE_TIME_MODIFIERS.length) { + if (fmt instanceof SimpleDateFormat) { + result.append(",date,").append(((SimpleDateFormat)fmt).toPattern()); + } else { + // UNKNOWN + } + } else if (index != MODIFIER_DEFAULT) { + result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]); + } + } else { + //result.append(", unknown"); + } + result.append('}'); + } + copyAndFixQuotes(pattern, lastOffset, pattern.length(), result); + return result.toString(); + } + + /** + * Sets the formats to use for the values passed into + * format methods or returned from parse + * methods. The indices of elements in newFormats + * correspond to the argument indices used in the previously set + * pattern string. + * The order of formats in newFormats thus corresponds to + * the order of elements in the arguments array passed + * to the format methods or the result array returned + * by the parse methods. + *

+ * If an argument index is used for more than one format element + * in the pattern string, then the corresponding new format is used + * for all such format elements. If an argument index is not used + * for any format element in the pattern string, then the + * corresponding new format is ignored. If fewer formats are provided + * than needed, then only the formats for argument indices less + * than newFormats.length are replaced. + * + * @param newFormats the new formats to use + * @exception NullPointerException if newFormats is null + * @since 1.4 + */ + public void setFormatsByArgumentIndex(Format[] newFormats) { + for (int i = 0; i <= maxOffset; i++) { + int j = argumentNumbers[i]; + if (j < newFormats.length) { + formats[i] = newFormats[j]; + } + } + } + + /** + * Sets the formats to use for the format elements in the + * previously set pattern string. + * The order of formats in newFormats corresponds to + * the order of format elements in the pattern string. + *

+ * If more formats are provided than needed by the pattern string, + * the remaining ones are ignored. If fewer formats are provided + * than needed, then only the first newFormats.length + * formats are replaced. + *

+ * Since the order of format elements in a pattern string often + * changes during localization, it is generally better to use the + * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex} + * method, which assumes an order of formats corresponding to the + * order of elements in the arguments array passed to + * the format methods or the result array returned by + * the parse methods. + * + * @param newFormats the new formats to use + * @exception NullPointerException if newFormats is null + */ + public void setFormats(Format[] newFormats) { + int runsToCopy = newFormats.length; + if (runsToCopy > maxOffset + 1) { + runsToCopy = maxOffset + 1; + } + for (int i = 0; i < runsToCopy; i++) { + formats[i] = newFormats[i]; + } + } + + /** + * Sets the format to use for the format elements within the + * previously set pattern string that use the given argument + * index. + * The argument index is part of the format element definition and + * represents an index into the arguments array passed + * to the format methods or the result array returned + * by the parse methods. + *

+ * If the argument index is used for more than one format element + * in the pattern string, then the new format is used for all such + * format elements. If the argument index is not used for any format + * element in the pattern string, then the new format is ignored. + * + * @param argumentIndex the argument index for which to use the new format + * @param newFormat the new format to use + * @since 1.4 + */ + public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) { + for (int j = 0; j <= maxOffset; j++) { + if (argumentNumbers[j] == argumentIndex) { + formats[j] = newFormat; + } + } + } + + /** + * Sets the format to use for the format element with the given + * format element index within the previously set pattern string. + * The format element index is the zero-based number of the format + * element counting from the start of the pattern string. + *

+ * Since the order of format elements in a pattern string often + * changes during localization, it is generally better to use the + * {@link #setFormatByArgumentIndex setFormatByArgumentIndex} + * method, which accesses format elements based on the argument + * index they specify. + * + * @param formatElementIndex the index of a format element within the pattern + * @param newFormat the format to use for the specified format element + * @exception ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or + * larger than the number of format elements in the pattern string + */ + public void setFormat(int formatElementIndex, Format newFormat) { + formats[formatElementIndex] = newFormat; + } + + /** + * Gets the formats used for the values passed into + * format methods or returned from parse + * methods. The indices of elements in the returned array + * correspond to the argument indices used in the previously set + * pattern string. + * The order of formats in the returned array thus corresponds to + * the order of elements in the arguments array passed + * to the format methods or the result array returned + * by the parse methods. + *

+ * If an argument index is used for more than one format element + * in the pattern string, then the format used for the last such + * format element is returned in the array. If an argument index + * is not used for any format element in the pattern string, then + * null is returned in the array. + * + * @return the formats used for the arguments within the pattern + * @since 1.4 + */ + public Format[] getFormatsByArgumentIndex() { + int maximumArgumentNumber = -1; + for (int i = 0; i <= maxOffset; i++) { + if (argumentNumbers[i] > maximumArgumentNumber) { + maximumArgumentNumber = argumentNumbers[i]; + } + } + Format[] resultArray = new Format[maximumArgumentNumber + 1]; + for (int i = 0; i <= maxOffset; i++) { + resultArray[argumentNumbers[i]] = formats[i]; + } + return resultArray; + } + + /** + * Gets the formats used for the format elements in the + * previously set pattern string. + * The order of formats in the returned array corresponds to + * the order of format elements in the pattern string. + *

+ * Since the order of format elements in a pattern string often + * changes during localization, it's generally better to use the + * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex} + * method, which assumes an order of formats corresponding to the + * order of elements in the arguments array passed to + * the format methods or the result array returned by + * the parse methods. + * + * @return the formats used for the format elements in the pattern + */ + public Format[] getFormats() { + Format[] resultArray = new Format[maxOffset + 1]; + System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1); + return resultArray; + } + + /** + * Formats an array of objects and appends the MessageFormat's + * pattern, with format elements replaced by the formatted objects, to the + * provided StringBuffer. + *

+ * The text substituted for the individual format elements is derived from + * the current subformat of the format element and the + * arguments element at the format element's argument index + * as indicated by the first matching line of the following table. An + * argument is unavailable if arguments is + * null or has fewer than argumentIndex+1 elements. + *

+ * + * + * + * + * + * + * + * + * + * + *
Subformat + * Argument + * Formatted Text + *
any + * unavailable + * "{" + argumentIndex + "}" + *
any + * null + * "null" + *
instanceof ChoiceFormat + * any + * subformat.format(argument).indexOf('{') >= 0 ?
+ * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) : + * subformat.format(argument)
+ *
!= null + * any + * subformat.format(argument) + *
null + * instanceof Number + * NumberFormat.getInstance(getLocale()).format(argument) + *
null + * instanceof Date + * DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument) + *
null + * instanceof String + * argument + *
null + * any + * argument.toString() + *
+ *

+ * If pos is non-null, and refers to + * Field.ARGUMENT, the location of the first formatted + * string will be returned. + * + * @param arguments an array of objects to be formatted and substituted. + * @param result where text is appended. + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception IllegalArgumentException if an argument in the + * arguments array is not of the type + * expected by the format element(s) that use it. + */ + public final StringBuffer format(Object[] arguments, StringBuffer result, + FieldPosition pos) + { + return subformat(arguments, result, pos, null); + } + + /** + * Creates a MessageFormat with the given pattern and uses it + * to format the given arguments. This is equivalent to + *

+ * (new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString() + *
+ * + * @exception IllegalArgumentException if the pattern is invalid, + * or if an argument in the arguments array + * is not of the type expected by the format element(s) + * that use it. + */ + public static String format(String pattern, Object ... arguments) { + MessageFormat temp = new MessageFormat(pattern); + return temp.format(arguments); + } + + // Overrides + /** + * Formats an array of objects and appends the MessageFormat's + * pattern, with format elements replaced by the formatted objects, to the + * provided StringBuffer. + * This is equivalent to + *
+ * {@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos) + *
+ * + * @param arguments an array of objects to be formatted and substituted. + * @param result where text is appended. + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception IllegalArgumentException if an argument in the + * arguments array is not of the type + * expected by the format element(s) that use it. + */ + public final StringBuffer format(Object arguments, StringBuffer result, + FieldPosition pos) + { + return subformat((Object[]) arguments, result, pos, null); + } + + /** + * Formats an array of objects and inserts them into the + * MessageFormat's pattern, producing an + * AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * The text of the returned AttributedCharacterIterator is + * the same that would be returned by + *

+ * {@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString() + *
+ *

+ * In addition, the AttributedCharacterIterator contains at + * least attributes indicating where text was generated from an + * argument in the arguments array. The keys of these attributes are of + * type MessageFormat.Field, their values are + * Integer objects indicating the index in the arguments + * array of the argument from which the text was generated. + *

+ * The attributes/value from the underlying Format + * instances that MessageFormat uses will also be + * placed in the resulting AttributedCharacterIterator. + * This allows you to not only find where an argument is placed in the + * resulting String, but also which fields it contains in turn. + * + * @param arguments an array of objects to be formatted and substituted. + * @return AttributedCharacterIterator describing the formatted value. + * @exception NullPointerException if arguments is null. + * @exception IllegalArgumentException if an argument in the + * arguments array is not of the type + * expected by the format element(s) that use it. + * @since 1.4 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object arguments) { + StringBuffer result = new StringBuffer(); + ArrayList iterators = new ArrayList(); + + if (arguments == null) { + throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + } + subformat((Object[]) arguments, result, null, iterators); + if (iterators.size() == 0) { + return createAttributedCharacterIterator(""); + } + return createAttributedCharacterIterator( + (AttributedCharacterIterator[])iterators.toArray( + new AttributedCharacterIterator[iterators.size()])); + } + + /** + * Parses the string. + * + *

Caveats: The parse may fail in a number of circumstances. + * For example: + *

    + *
  • If one of the arguments does not occur in the pattern. + *
  • If the format of an argument loses information, such as + * with a choice format where a large number formats to "many". + *
  • Does not yet handle recursion (where + * the substituted strings contain {n} references.) + *
  • Will not always find a match (or the correct match) + * if some part of the parse is ambiguous. + * For example, if the pattern "{1},{2}" is used with the + * string arguments {"a,b", "c"}, it will format as "a,b,c". + * When the result is parsed, it will return {"a", "b,c"}. + *
  • If a single argument is parsed more than once in the string, + * then the later parse wins. + *
+ * When the parse fails, use ParsePosition.getErrorIndex() to find out + * where in the string the parsing failed. The returned error + * index is the starting offset of the sub-patterns that the string + * is comparing with. For example, if the parsing string "AAA {0} BBB" + * is comparing against the pattern "AAD {0} BBB", the error index is + * 0. When an error occurs, the call to this method will return null. + * If the source is null, return an empty array. + */ + public Object[] parse(String source, ParsePosition pos) { + if (source == null) { + Object[] empty = {}; + return empty; + } + + int maximumArgumentNumber = -1; + for (int i = 0; i <= maxOffset; i++) { + if (argumentNumbers[i] > maximumArgumentNumber) { + maximumArgumentNumber = argumentNumbers[i]; + } + } + Object[] resultArray = new Object[maximumArgumentNumber + 1]; + + int patternOffset = 0; + int sourceOffset = pos.index; + ParsePosition tempStatus = new ParsePosition(0); + for (int i = 0; i <= maxOffset; ++i) { + // match up to format + int len = offsets[i] - patternOffset; + if (len == 0 || pattern.regionMatches(patternOffset, + source, sourceOffset, len)) { + sourceOffset += len; + patternOffset += len; + } else { + pos.errorIndex = sourceOffset; + return null; // leave index as is to signal error + } + + // now use format + if (formats[i] == null) { // string format + // if at end, use longest possible match + // otherwise uses first match to intervening string + // does NOT recursively try all possibilities + int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length(); + + int next; + if (patternOffset >= tempLength) { + next = source.length(); + }else{ + next = source.indexOf(pattern.substring(patternOffset, tempLength), + sourceOffset); + } + + if (next < 0) { + pos.errorIndex = sourceOffset; + return null; // leave index as is to signal error + } else { + String strValue= source.substring(sourceOffset,next); + if (!strValue.equals("{"+argumentNumbers[i]+"}")) + resultArray[argumentNumbers[i]] + = source.substring(sourceOffset,next); + sourceOffset = next; + } + } else { + tempStatus.index = sourceOffset; + resultArray[argumentNumbers[i]] + = formats[i].parseObject(source,tempStatus); + if (tempStatus.index == sourceOffset) { + pos.errorIndex = sourceOffset; + return null; // leave index as is to signal error + } + sourceOffset = tempStatus.index; // update + } + } + int len = pattern.length() - patternOffset; + if (len == 0 || pattern.regionMatches(patternOffset, + source, sourceOffset, len)) { + pos.index = sourceOffset + len; + } else { + pos.errorIndex = sourceOffset; + return null; // leave index as is to signal error + } + return resultArray; + } + + /** + * Parses text from the beginning of the given string to produce an object + * array. + * The method may not use the entire text of the given string. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on message parsing. + * + * @param source A String whose beginning should be parsed. + * @return An Object array parsed from the string. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public Object[] parse(String source) throws ParseException { + ParsePosition pos = new ParsePosition(0); + Object[] result = parse(source, pos); + if (pos.index == 0) // unchanged, returned object is null + throw new ParseException("MessageFormat parse error!", pos.errorIndex); + + return result; + } + + /** + * Parses text from a string to produce an object array. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * object array is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on message parsing. + * + * @param source A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return An Object array parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if pos is null. + */ + public Object parseObject(String source, ParsePosition pos) { + return parse(source, pos); + } + + /** + * Creates and returns a copy of this object. + * + * @return a clone of this instance. + */ + public Object clone() { + MessageFormat other = (MessageFormat) super.clone(); + + // clone arrays. Can't do with utility because of bug in Cloneable + other.formats = (Format[]) formats.clone(); // shallow clone + for (int i = 0; i < formats.length; ++i) { + if (formats[i] != null) + other.formats[i] = (Format)formats[i].clone(); + } + // for primitives or immutables, shallow clone is enough + other.offsets = (int[]) offsets.clone(); + other.argumentNumbers = (int[]) argumentNumbers.clone(); + + return other; + } + + /** + * Equality comparison between two message format objects + */ + public boolean equals(Object obj) { + if (this == obj) // quick check + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + MessageFormat other = (MessageFormat) obj; + return (maxOffset == other.maxOffset + && pattern.equals(other.pattern) + && ((locale != null && locale.equals(other.locale)) + || (locale == null && other.locale == null)) + && Arrays.equals(offsets,other.offsets) + && Arrays.equals(argumentNumbers,other.argumentNumbers) + && Arrays.equals(formats,other.formats)); + } + + /** + * Generates a hash code for the message format object. + */ + public int hashCode() { + return pattern.hashCode(); // enough for reasonable distribution + } + + + /** + * Defines constants that are used as attribute keys in the + * AttributedCharacterIterator returned + * from MessageFormat.formatToCharacterIterator. + * + * @since 1.4 + */ + public static class Field extends Format.Field { + + // Proclaim serial compatibility with 1.4 FCS + private static final long serialVersionUID = 7899943957617360810L; + + /** + * Creates a Field with the specified name. + * + * @param name Name of the attribute + */ + protected Field(String name) { + super(name); + } + + /** + * Resolves instances being deserialized to the predefined constants. + * + * @throws InvalidObjectException if the constant could not be + * resolved. + * @return resolved MessageFormat.Field constant + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != MessageFormat.Field.class) { + throw new InvalidObjectException("subclass didn't correctly implement readResolve"); + } + + return ARGUMENT; + } + + // + // The constants + // + + /** + * Constant identifying a portion of a message that was generated + * from an argument passed into formatToCharacterIterator. + * The value associated with the key will be an Integer + * indicating the index in the arguments array of the + * argument from which the text was generated. + */ + public final static Field ARGUMENT = + new Field("message argument field"); + } + + // ===========================privates============================ + + /** + * The locale to use for formatting numbers and dates. + * @serial + */ + private Locale locale; + + /** + * The string that the formatted values are to be plugged into. In other words, this + * is the pattern supplied on construction with all of the {} expressions taken out. + * @serial + */ + private String pattern = ""; + + /** The initially expected number of subformats in the format */ + private static final int INITIAL_FORMATS = 10; + + /** + * An array of formatters, which are used to format the arguments. + * @serial + */ + private Format[] formats = new Format[INITIAL_FORMATS]; + + /** + * The positions where the results of formatting each argument are to be inserted + * into the pattern. + * @serial + */ + private int[] offsets = new int[INITIAL_FORMATS]; + + /** + * The argument numbers corresponding to each formatter. (The formatters are stored + * in the order they occur in the pattern, not in the order in which the arguments + * are specified.) + * @serial + */ + private int[] argumentNumbers = new int[INITIAL_FORMATS]; + + /** + * One less than the number of entries in offsets. Can also be thought of + * as the index of the highest-numbered element in offsets that is being used. + * All of these arrays should have the same number of elements being used as offsets + * does, and so this variable suffices to tell us how many entries are in all of them. + * @serial + */ + private int maxOffset = -1; + + /** + * Internal routine used by format. If characterIterators is + * non-null, AttributedCharacterIterator will be created from the + * subformats as necessary. If characterIterators is null + * and fp is non-null and identifies + * Field.MESSAGE_ARGUMENT, the location of + * the first replaced argument will be set in it. + * + * @exception IllegalArgumentException if an argument in the + * arguments array is not of the type + * expected by the format element(s) that use it. + */ + private StringBuffer subformat(Object[] arguments, StringBuffer result, + FieldPosition fp, List characterIterators) { + // note: this implementation assumes a fast substring & index. + // if this is not true, would be better to append chars one by one. + int lastOffset = 0; + int last = result.length(); + for (int i = 0; i <= maxOffset; ++i) { + result.append(pattern.substring(lastOffset, offsets[i])); + lastOffset = offsets[i]; + int argumentNumber = argumentNumbers[i]; + if (arguments == null || argumentNumber >= arguments.length) { + result.append('{').append(argumentNumber).append('}'); + continue; + } + // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3); + if (false) { // if (argRecursion == 3){ + // prevent loop!!! + result.append('\uFFFD'); + } else { + Object obj = arguments[argumentNumber]; + String arg = null; + Format subFormatter = null; + if (obj == null) { + arg = "null"; + } else if (formats[i] != null) { + subFormatter = formats[i]; + if (subFormatter instanceof ChoiceFormat) { + arg = formats[i].format(obj); + if (arg.indexOf('{') >= 0) { + subFormatter = new MessageFormat(arg, locale); + obj = arguments; + arg = null; + } + } + } else if (obj instanceof Number) { + // format number if can + subFormatter = NumberFormat.getInstance(locale); + } else if (obj instanceof Date) { + // format a Date if can + subFormatter = DateFormat.getDateTimeInstance( + DateFormat.SHORT, DateFormat.SHORT, locale);//fix + } else if (obj instanceof String) { + arg = (String) obj; + + } else { + arg = obj.toString(); + if (arg == null) arg = "null"; + } + + // At this point we are in two states, either subFormatter + // is non-null indicating we should format obj using it, + // or arg is non-null and we should use it as the value. + + if (characterIterators != null) { + // If characterIterators is non-null, it indicates we need + // to get the CharacterIterator from the child formatter. + if (last != result.length()) { + characterIterators.add( + createAttributedCharacterIterator(result.substring + (last))); + last = result.length(); + } + if (subFormatter != null) { + AttributedCharacterIterator subIterator = + subFormatter.formatToCharacterIterator(obj); + + append(result, subIterator); + if (last != result.length()) { + characterIterators.add( + createAttributedCharacterIterator( + subIterator, Field.ARGUMENT, + Integer.valueOf(argumentNumber))); + last = result.length(); + } + arg = null; + } + if (arg != null && arg.length() > 0) { + result.append(arg); + characterIterators.add( + createAttributedCharacterIterator( + arg, Field.ARGUMENT, + Integer.valueOf(argumentNumber))); + last = result.length(); + } + } + else { + if (subFormatter != null) { + arg = subFormatter.format(obj); + } + last = result.length(); + result.append(arg); + if (i == 0 && fp != null && Field.ARGUMENT.equals( + fp.getFieldAttribute())) { + fp.setBeginIndex(last); + fp.setEndIndex(result.length()); + } + last = result.length(); + } + } + } + result.append(pattern.substring(lastOffset, pattern.length())); + if (characterIterators != null && last != result.length()) { + characterIterators.add(createAttributedCharacterIterator( + result.substring(last))); + } + return result; + } + + /** + * Convenience method to append all the characters in + * iterator to the StringBuffer result. + */ + private void append(StringBuffer result, CharacterIterator iterator) { + if (iterator.first() != CharacterIterator.DONE) { + char aChar; + + result.append(iterator.first()); + while ((aChar = iterator.next()) != CharacterIterator.DONE) { + result.append(aChar); + } + } + } + + // Indices for segments + private static final int SEG_RAW = 0; + private static final int SEG_INDEX = 1; + private static final int SEG_TYPE = 2; + private static final int SEG_MODIFIER = 3; // modifier or subformat + + // Indices for type keywords + private static final int TYPE_NULL = 0; + private static final int TYPE_NUMBER = 1; + private static final int TYPE_DATE = 2; + private static final int TYPE_TIME = 3; + private static final int TYPE_CHOICE = 4; + + private static final String[] TYPE_KEYWORDS = { + "", + "number", + "date", + "time", + "choice" + }; + + // Indices for number modifiers + private static final int MODIFIER_DEFAULT = 0; // common in number and date-time + private static final int MODIFIER_CURRENCY = 1; + private static final int MODIFIER_PERCENT = 2; + private static final int MODIFIER_INTEGER = 3; + + private static final String[] NUMBER_MODIFIER_KEYWORDS = { + "", + "currency", + "percent", + "integer" + }; + + // Indices for date-time modifiers + private static final int MODIFIER_SHORT = 1; + private static final int MODIFIER_MEDIUM = 2; + private static final int MODIFIER_LONG = 3; + private static final int MODIFIER_FULL = 4; + + private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { + "", + "short", + "medium", + "long", + "full" + }; + + // Date-time style values corresponding to the date-time modifiers. + private static final int[] DATE_TIME_MODIFIERS = { + DateFormat.DEFAULT, + DateFormat.SHORT, + DateFormat.MEDIUM, + DateFormat.LONG, + DateFormat.FULL, + }; + + private void makeFormat(int position, int offsetNumber, + StringBuilder[] textSegments) + { + String[] segments = new String[textSegments.length]; + for (int i = 0; i < textSegments.length; i++) { + StringBuilder oneseg = textSegments[i]; + segments[i] = (oneseg != null) ? oneseg.toString() : ""; + } + + // get the argument number + int argumentNumber; + try { + argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized! + } catch (NumberFormatException e) { + throw new IllegalArgumentException("can't parse argument number: " + + segments[SEG_INDEX], e); + } + if (argumentNumber < 0) { + throw new IllegalArgumentException("negative argument number: " + + argumentNumber); + } + + // resize format information arrays if necessary + if (offsetNumber >= formats.length) { + int newLength = formats.length * 2; + Format[] newFormats = new Format[newLength]; + int[] newOffsets = new int[newLength]; + int[] newArgumentNumbers = new int[newLength]; + System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1); + System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1); + System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1); + formats = newFormats; + offsets = newOffsets; + argumentNumbers = newArgumentNumbers; + } + int oldMaxOffset = maxOffset; + maxOffset = offsetNumber; + offsets[offsetNumber] = segments[SEG_RAW].length(); + argumentNumbers[offsetNumber] = argumentNumber; + + // now get the format + Format newFormat = null; + if (segments[SEG_TYPE].length() != 0) { + int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); + switch (type) { + case TYPE_NULL: + // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}" + // are treated as "{0}". + break; + + case TYPE_NUMBER: + switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { + case MODIFIER_DEFAULT: + newFormat = NumberFormat.getInstance(locale); + break; + case MODIFIER_CURRENCY: + newFormat = NumberFormat.getCurrencyInstance(locale); + break; + case MODIFIER_PERCENT: + newFormat = NumberFormat.getPercentInstance(locale); + break; + case MODIFIER_INTEGER: + newFormat = NumberFormat.getIntegerInstance(locale); + break; + default: // DecimalFormat pattern + try { + newFormat = new DecimalFormat(segments[SEG_MODIFIER], + DecimalFormatSymbols.getInstance(locale)); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + throw e; + } + break; + } + break; + + case TYPE_DATE: + case TYPE_TIME: + int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); + if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { + if (type == TYPE_DATE) { + newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod], + locale); + } else { + newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod], + locale); + } + } else { + // SimpleDateFormat pattern + try { + newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale); + } catch (IllegalArgumentException e) { + maxOffset = oldMaxOffset; + throw e; + } + } + break; + + case TYPE_CHOICE: + try { + // ChoiceFormat pattern + newFormat = new ChoiceFormat(segments[SEG_MODIFIER]); + } catch (Exception e) { + maxOffset = oldMaxOffset; + throw new IllegalArgumentException("Choice Pattern incorrect: " + + segments[SEG_MODIFIER], e); + } + break; + + default: + maxOffset = oldMaxOffset; + throw new IllegalArgumentException("unknown format type: " + + segments[SEG_TYPE]); + } + } + formats[offsetNumber] = newFormat; + } + + private static final int findKeyword(String s, String[] list) { + for (int i = 0; i < list.length; ++i) { + if (s.equals(list[i])) + return i; + } + + // Try trimmed lowercase. + String ls = s.trim().toLowerCase(Locale.ROOT); + if (ls != s) { + for (int i = 0; i < list.length; ++i) { + if (ls.equals(list[i])) + return i; + } + } + return -1; + } + + private static final void copyAndFixQuotes(String source, int start, int end, + StringBuilder target) { + boolean quoted = false; + + for (int i = start; i < end; ++i) { + char ch = source.charAt(i); + if (ch == '{') { + if (!quoted) { + target.append('\''); + quoted = true; + } + target.append(ch); + } else if (ch == '\'') { + target.append("''"); + } else { + if (quoted) { + target.append('\''); + quoted = false; + } + target.append(ch); + } + } + if (quoted) { + target.append('\''); + } + } + + /** + * After reading an object from the input stream, do a simple verification + * to maintain class invariants. + * @throws InvalidObjectException if the objects read from the stream is invalid. + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + boolean isValid = maxOffset >= -1 + && formats.length > maxOffset + && offsets.length > maxOffset + && argumentNumbers.length > maxOffset; + if (isValid) { + int lastOffset = pattern.length() + 1; + for (int i = maxOffset; i >= 0; --i) { + if ((offsets[i] < 0) || (offsets[i] > lastOffset)) { + isValid = false; + break; + } else { + lastOffset = offsets[i]; + } + } + } + if (!isValid) { + throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream."); + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/NumberFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/NumberFormat.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,1162 @@ +/* + * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.InvalidObjectException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.text.spi.NumberFormatProvider; +import java.util.Currency; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.spi.LocaleServiceProvider; +import sun.util.LocaleServiceProviderPool; +import sun.util.resources.LocaleData; + +/** + * NumberFormat is the abstract base class for all number + * formats. This class provides the interface for formatting and parsing + * numbers. NumberFormat also provides methods for determining + * which locales have number formats, and what their names are. + * + *

+ * NumberFormat helps you to format and parse numbers for any locale. + * Your code can be completely independent of the locale conventions for + * decimal points, thousands-separators, or even the particular decimal + * digits used, or whether the number format is even decimal. + * + *

+ * To format a number for the current Locale, use one of the factory + * class methods: + *

+ *
+ *  myString = NumberFormat.getInstance().format(myNumber);
+ * 
+ *
+ * If you are formatting multiple numbers, it is + * more efficient to get the format and use it multiple times so that + * the system doesn't have to fetch the information about the local + * language and country conventions multiple times. + *
+ *
+ * NumberFormat nf = NumberFormat.getInstance();
+ * for (int i = 0; i < myNumber.length; ++i) {
+ *     output.println(nf.format(myNumber[i]) + "; ");
+ * }
+ * 
+ *
+ * To format a number for a different Locale, specify it in the + * call to getInstance. + *
+ *
+ * NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH);
+ * 
+ *
+ * You can also use a NumberFormat to parse numbers: + *
+ *
+ * myNumber = nf.parse(myString);
+ * 
+ *
+ * Use getInstance or getNumberInstance to get the + * normal number format. Use getIntegerInstance to get an + * integer number format. Use getCurrencyInstance to get the + * currency number format. And use getPercentInstance to get a + * format for displaying percentages. With this format, a fraction like + * 0.53 is displayed as 53%. + * + *

+ * You can also control the display of numbers with such methods as + * setMinimumFractionDigits. + * If you want even more control over the format or parsing, + * or want to give your users more control, + * you can try casting the NumberFormat you get from the factory methods + * to a DecimalFormat. This will work for the vast majority + * of locales; just remember to put it in a try block in case you + * encounter an unusual one. + * + *

+ * NumberFormat and DecimalFormat are designed such that some controls + * work for formatting and others work for parsing. The following is + * the detailed description for each these control methods, + *

+ * setParseIntegerOnly : only affects parsing, e.g. + * if true, "3456.78" -> 3456 (and leaves the parse position just after index 6) + * if false, "3456.78" -> 3456.78 (and leaves the parse position just after index 8) + * This is independent of formatting. If you want to not show a decimal point + * where there might be no digits after the decimal point, use + * setDecimalSeparatorAlwaysShown. + *

+ * setDecimalSeparatorAlwaysShown : only affects formatting, and only where + * there might be no digits after the decimal point, such as with a pattern + * like "#,##0.##", e.g., + * if true, 3456.00 -> "3,456." + * if false, 3456.00 -> "3456" + * This is independent of parsing. If you want parsing to stop at the decimal + * point, use setParseIntegerOnly. + * + *

+ * You can also use forms of the parse and format + * methods with ParsePosition and FieldPosition to + * allow you to: + *

    + *
  • progressively parse through pieces of a string + *
  • align the decimal point and other areas + *
+ * For example, you can align numbers in two ways: + *
    + *
  1. If you are using a monospaced font with spacing for alignment, + * you can pass the FieldPosition in your format call, with + * field = INTEGER_FIELD. On output, + * getEndIndex will be set to the offset between the + * last character of the integer and the decimal. Add + * (desiredSpaceCount - getEndIndex) spaces at the front of the string. + * + *
  2. If you are using proportional fonts, + * instead of padding with spaces, measure the width + * of the string in pixels from the start to getEndIndex. + * Then move the pen by + * (desiredPixelWidth - widthToAlignmentPoint) before drawing the text. + * It also works where there is no decimal, but possibly additional + * characters at the end, e.g., with parentheses in negative + * numbers: "(12)" for -12. + *
+ * + *

Synchronization

+ * + *

+ * Number formats are generally not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see DecimalFormat + * @see ChoiceFormat + * @author Mark Davis + * @author Helena Shih + */ +public abstract class NumberFormat extends Format { + + /** + * Field constant used to construct a FieldPosition object. Signifies that + * the position of the integer part of a formatted number should be returned. + * @see java.text.FieldPosition + */ + public static final int INTEGER_FIELD = 0; + + /** + * Field constant used to construct a FieldPosition object. Signifies that + * the position of the fraction part of a formatted number should be returned. + * @see java.text.FieldPosition + */ + public static final int FRACTION_FIELD = 1; + + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + protected NumberFormat() { + } + + /** + * Formats a number and appends the resulting text to the given string + * buffer. + * The number can be of any subclass of {@link java.lang.Number}. + *

+ * This implementation extracts the number's value using + * {@link java.lang.Number#longValue()} for all integral type values that + * can be converted to long without loss of information, + * including BigInteger values with a + * {@link java.math.BigInteger#bitLength() bit length} of less than 64, + * and {@link java.lang.Number#doubleValue()} for all other types. It + * then calls + * {@link #format(long,java.lang.StringBuffer,java.text.FieldPosition)} + * or {@link #format(double,java.lang.StringBuffer,java.text.FieldPosition)}. + * This may result in loss of magnitude information and precision for + * BigInteger and BigDecimal values. + * @param number the number to format + * @param toAppendTo the StringBuffer to which the formatted + * text is to be appended + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return the value passed in as toAppendTo + * @exception IllegalArgumentException if number is + * null or not an instance of Number. + * @exception NullPointerException if toAppendTo or + * pos is null + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + public StringBuffer format(Object number, + StringBuffer toAppendTo, + FieldPosition pos) { + if (number instanceof Long || number instanceof Integer || + number instanceof Short || number instanceof Byte || + number instanceof AtomicInteger || number instanceof AtomicLong || + (number instanceof BigInteger && + ((BigInteger)number).bitLength() < 64)) { + return format(((Number)number).longValue(), toAppendTo, pos); + } else if (number instanceof Number) { + return format(((Number)number).doubleValue(), toAppendTo, pos); + } else { + throw new IllegalArgumentException("Cannot format given Object as a Number"); + } + } + + /** + * Parses text from a string to produce a Number. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * number is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on number parsing. + * + * @param source A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return A Number parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if pos is null. + */ + public final Object parseObject(String source, ParsePosition pos) { + return parse(source, pos); + } + + /** + * Specialization of format. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.Format#format + */ + public final String format(double number) { + return format(number, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } + + /** + * Specialization of format. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.Format#format + */ + public final String format(long number) { + return format(number, new StringBuffer(), + DontCareFieldPosition.INSTANCE).toString(); + } + + /** + * Specialization of format. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.Format#format + */ + public abstract StringBuffer format(double number, + StringBuffer toAppendTo, + FieldPosition pos); + + /** + * Specialization of format. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.Format#format + */ + public abstract StringBuffer format(long number, + StringBuffer toAppendTo, + FieldPosition pos); + + /** + * Returns a Long if possible (e.g., within the range [Long.MIN_VALUE, + * Long.MAX_VALUE] and with no decimals), otherwise a Double. + * If IntegerOnly is set, will stop at a decimal + * point (or equivalent; e.g., for rational numbers "1 2/3", will stop + * after the 1). + * Does not throw an exception; if no object can be parsed, index is + * unchanged! + * @see java.text.NumberFormat#isParseIntegerOnly + * @see java.text.Format#parseObject + */ + public abstract Number parse(String source, ParsePosition parsePosition); + + /** + * Parses text from the beginning of the given string to produce a number. + * The method may not use the entire text of the given string. + *

+ * See the {@link #parse(String, ParsePosition)} method for more information + * on number parsing. + * + * @param source A String whose beginning should be parsed. + * @return A Number parsed from the string. + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + */ + public Number parse(String source) throws ParseException { + ParsePosition parsePosition = new ParsePosition(0); + Number result = parse(source, parsePosition); + if (parsePosition.index == 0) { + throw new ParseException("Unparseable number: \"" + source + "\"", + parsePosition.errorIndex); + } + return result; + } + + /** + * Returns true if this format will parse numbers as integers only. + * For example in the English locale, with ParseIntegerOnly true, the + * string "1234." would be parsed as the integer value 1234 and parsing + * would stop at the "." character. Of course, the exact format accepted + * by the parse operation is locale dependant and determined by sub-classes + * of NumberFormat. + */ + public boolean isParseIntegerOnly() { + return parseIntegerOnly; + } + + /** + * Sets whether or not numbers should be parsed as integers only. + * @see #isParseIntegerOnly + */ + public void setParseIntegerOnly(boolean value) { + parseIntegerOnly = value; + } + + //============== Locale Stuff ===================== + + /** + * Returns a general-purpose number format for the current default locale. + * This is the same as calling + * {@link #getNumberInstance() getNumberInstance()}. + */ + public final static NumberFormat getInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); + } + + /** + * Returns a general-purpose number format for the specified locale. + * This is the same as calling + * {@link #getNumberInstance(java.util.Locale) getNumberInstance(inLocale)}. + */ + public static NumberFormat getInstance(Locale inLocale) { + return getInstance(inLocale, NUMBERSTYLE); + } + + /** + * Returns a general-purpose number format for the current default locale. + */ + public final static NumberFormat getNumberInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE); + } + + /** + * Returns a general-purpose number format for the specified locale. + */ + public static NumberFormat getNumberInstance(Locale inLocale) { + return getInstance(inLocale, NUMBERSTYLE); + } + + /** + * Returns an integer number format for the current default locale. The + * returned number format is configured to round floating point numbers + * to the nearest integer using half-even rounding (see {@link + * java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}) for formatting, + * and to parse only the integer part of an input string (see {@link + * #isParseIntegerOnly isParseIntegerOnly}). + * + * @see #getRoundingMode() + * @return a number format for integer values + * @since 1.4 + */ + public final static NumberFormat getIntegerInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), INTEGERSTYLE); + } + + /** + * Returns an integer number format for the specified locale. The + * returned number format is configured to round floating point numbers + * to the nearest integer using half-even rounding (see {@link + * java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}) for formatting, + * and to parse only the integer part of an input string (see {@link + * #isParseIntegerOnly isParseIntegerOnly}). + * + * @see #getRoundingMode() + * @return a number format for integer values + * @since 1.4 + */ + public static NumberFormat getIntegerInstance(Locale inLocale) { + return getInstance(inLocale, INTEGERSTYLE); + } + + /** + * Returns a currency format for the current default locale. + */ + public final static NumberFormat getCurrencyInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), CURRENCYSTYLE); + } + + /** + * Returns a currency format for the specified locale. + */ + public static NumberFormat getCurrencyInstance(Locale inLocale) { + return getInstance(inLocale, CURRENCYSTYLE); + } + + /** + * Returns a percentage format for the current default locale. + */ + public final static NumberFormat getPercentInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), PERCENTSTYLE); + } + + /** + * Returns a percentage format for the specified locale. + */ + public static NumberFormat getPercentInstance(Locale inLocale) { + return getInstance(inLocale, PERCENTSTYLE); + } + + /** + * Returns a scientific format for the current default locale. + */ + /*public*/ final static NumberFormat getScientificInstance() { + return getInstance(Locale.getDefault(Locale.Category.FORMAT), SCIENTIFICSTYLE); + } + + /** + * Returns a scientific format for the specified locale. + */ + /*public*/ static NumberFormat getScientificInstance(Locale inLocale) { + return getInstance(inLocale, SCIENTIFICSTYLE); + } + + /** + * Returns an array of all locales for which the + * get*Instance methods of this class can return + * localized instances. + * The returned array represents the union of locales supported by the Java + * runtime and by installed + * {@link java.text.spi.NumberFormatProvider NumberFormatProvider} implementations. + * It must contain at least a Locale instance equal to + * {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * NumberFormat instances are available. + */ + public static Locale[] getAvailableLocales() { + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(NumberFormatProvider.class); + return pool.getAvailableLocales(); + } + + /** + * Overrides hashCode + */ + public int hashCode() { + return maximumIntegerDigits * 37 + maxFractionDigits; + // just enough fields for a reasonable distribution + } + + /** + * Overrides equals + */ + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (getClass() != obj.getClass()) { + return false; + } + NumberFormat other = (NumberFormat) obj; + return (maximumIntegerDigits == other.maximumIntegerDigits + && minimumIntegerDigits == other.minimumIntegerDigits + && maximumFractionDigits == other.maximumFractionDigits + && minimumFractionDigits == other.minimumFractionDigits + && groupingUsed == other.groupingUsed + && parseIntegerOnly == other.parseIntegerOnly); + } + + /** + * Overrides Cloneable + */ + public Object clone() { + NumberFormat other = (NumberFormat) super.clone(); + return other; + } + + /** + * Returns true if grouping is used in this format. For example, in the + * English locale, with grouping on, the number 1234567 might be formatted + * as "1,234,567". The grouping separator as well as the size of each group + * is locale dependant and is determined by sub-classes of NumberFormat. + * @see #setGroupingUsed + */ + public boolean isGroupingUsed() { + return groupingUsed; + } + + /** + * Set whether or not grouping will be used in this format. + * @see #isGroupingUsed + */ + public void setGroupingUsed(boolean newValue) { + groupingUsed = newValue; + } + + /** + * Returns the maximum number of digits allowed in the integer portion of a + * number. + * @see #setMaximumIntegerDigits + */ + public int getMaximumIntegerDigits() { + return maximumIntegerDigits; + } + + /** + * Sets the maximum number of digits allowed in the integer portion of a + * number. maximumIntegerDigits must be >= minimumIntegerDigits. If the + * new value for maximumIntegerDigits is less than the current value + * of minimumIntegerDigits, then minimumIntegerDigits will also be set to + * the new value. + * @param newValue the maximum number of integer digits to be shown; if + * less than zero, then zero is used. The concrete subclass may enforce an + * upper limit to this value appropriate to the numeric type being formatted. + * @see #getMaximumIntegerDigits + */ + public void setMaximumIntegerDigits(int newValue) { + maximumIntegerDigits = Math.max(0,newValue); + if (minimumIntegerDigits > maximumIntegerDigits) { + minimumIntegerDigits = maximumIntegerDigits; + } + } + + /** + * Returns the minimum number of digits allowed in the integer portion of a + * number. + * @see #setMinimumIntegerDigits + */ + public int getMinimumIntegerDigits() { + return minimumIntegerDigits; + } + + /** + * Sets the minimum number of digits allowed in the integer portion of a + * number. minimumIntegerDigits must be <= maximumIntegerDigits. If the + * new value for minimumIntegerDigits exceeds the current value + * of maximumIntegerDigits, then maximumIntegerDigits will also be set to + * the new value + * @param newValue the minimum number of integer digits to be shown; if + * less than zero, then zero is used. The concrete subclass may enforce an + * upper limit to this value appropriate to the numeric type being formatted. + * @see #getMinimumIntegerDigits + */ + public void setMinimumIntegerDigits(int newValue) { + minimumIntegerDigits = Math.max(0,newValue); + if (minimumIntegerDigits > maximumIntegerDigits) { + maximumIntegerDigits = minimumIntegerDigits; + } + } + + /** + * Returns the maximum number of digits allowed in the fraction portion of a + * number. + * @see #setMaximumFractionDigits + */ + public int getMaximumFractionDigits() { + return maximumFractionDigits; + } + + /** + * Sets the maximum number of digits allowed in the fraction portion of a + * number. maximumFractionDigits must be >= minimumFractionDigits. If the + * new value for maximumFractionDigits is less than the current value + * of minimumFractionDigits, then minimumFractionDigits will also be set to + * the new value. + * @param newValue the maximum number of fraction digits to be shown; if + * less than zero, then zero is used. The concrete subclass may enforce an + * upper limit to this value appropriate to the numeric type being formatted. + * @see #getMaximumFractionDigits + */ + public void setMaximumFractionDigits(int newValue) { + maximumFractionDigits = Math.max(0,newValue); + if (maximumFractionDigits < minimumFractionDigits) { + minimumFractionDigits = maximumFractionDigits; + } + } + + /** + * Returns the minimum number of digits allowed in the fraction portion of a + * number. + * @see #setMinimumFractionDigits + */ + public int getMinimumFractionDigits() { + return minimumFractionDigits; + } + + /** + * Sets the minimum number of digits allowed in the fraction portion of a + * number. minimumFractionDigits must be <= maximumFractionDigits. If the + * new value for minimumFractionDigits exceeds the current value + * of maximumFractionDigits, then maximumIntegerDigits will also be set to + * the new value + * @param newValue the minimum number of fraction digits to be shown; if + * less than zero, then zero is used. The concrete subclass may enforce an + * upper limit to this value appropriate to the numeric type being formatted. + * @see #getMinimumFractionDigits + */ + public void setMinimumFractionDigits(int newValue) { + minimumFractionDigits = Math.max(0,newValue); + if (maximumFractionDigits < minimumFractionDigits) { + maximumFractionDigits = minimumFractionDigits; + } + } + + /** + * Gets the currency used by this number format when formatting + * currency values. The initial value is derived in a locale dependent + * way. The returned value may be null if no valid + * currency could be determined and no currency has been set using + * {@link #setCurrency(java.util.Currency) setCurrency}. + *

+ * The default implementation throws + * UnsupportedOperationException. + * + * @return the currency used by this number format, or null + * @exception UnsupportedOperationException if the number format class + * doesn't implement currency formatting + * @since 1.4 + */ + public Currency getCurrency() { + throw new UnsupportedOperationException(); + } + + /** + * Sets the currency used by this number format when formatting + * currency values. This does not update the minimum or maximum + * number of fraction digits used by the number format. + *

+ * The default implementation throws + * UnsupportedOperationException. + * + * @param currency the new currency to be used by this number format + * @exception UnsupportedOperationException if the number format class + * doesn't implement currency formatting + * @exception NullPointerException if currency is null + * @since 1.4 + */ + public void setCurrency(Currency currency) { + throw new UnsupportedOperationException(); + } + + /** + * Gets the {@link java.math.RoundingMode} used in this NumberFormat. + * The default implementation of this method in NumberFormat + * always throws {@link java.lang.UnsupportedOperationException}. + * Subclasses which handle different rounding modes should override + * this method. + * + * @exception UnsupportedOperationException The default implementation + * always throws this exception + * @return The RoundingMode used for this NumberFormat. + * @see #setRoundingMode(RoundingMode) + * @since 1.6 + */ + public RoundingMode getRoundingMode() { + throw new UnsupportedOperationException(); + } + + /** + * Sets the {@link java.math.RoundingMode} used in this NumberFormat. + * The default implementation of this method in NumberFormat always + * throws {@link java.lang.UnsupportedOperationException}. + * Subclasses which handle different rounding modes should override + * this method. + * + * @exception UnsupportedOperationException The default implementation + * always throws this exception + * @exception NullPointerException if roundingMode is null + * @param roundingMode The RoundingMode to be used + * @see #getRoundingMode() + * @since 1.6 + */ + public void setRoundingMode(RoundingMode roundingMode) { + throw new UnsupportedOperationException(); + } + + // =======================privates=============================== + + private static NumberFormat getInstance(Locale desiredLocale, + int choice) { + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(NumberFormatProvider.class); + if (pool.hasProviders()) { + NumberFormat providersInstance = pool.getLocalizedObject( + NumberFormatGetter.INSTANCE, + desiredLocale, + choice); + if (providersInstance != null) { + return providersInstance; + } + } + + /* try the cache first */ + String[] numberPatterns = (String[])cachedLocaleData.get(desiredLocale); + if (numberPatterns == null) { /* cache miss */ + ResourceBundle resource = LocaleData.getNumberFormatData(desiredLocale); + numberPatterns = resource.getStringArray("NumberPatterns"); + /* update cache */ + cachedLocaleData.put(desiredLocale, numberPatterns); + } + + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(desiredLocale); + int entry = (choice == INTEGERSTYLE) ? NUMBERSTYLE : choice; + DecimalFormat format = new DecimalFormat(numberPatterns[entry], symbols); + + if (choice == INTEGERSTYLE) { + format.setMaximumFractionDigits(0); + format.setDecimalSeparatorAlwaysShown(false); + format.setParseIntegerOnly(true); + } else if (choice == CURRENCYSTYLE) { + format.adjustForCurrencyDefaultFractionDigits(); + } + + return format; + } + + /** + * First, read in the default serializable data. + * + * Then, if serialVersionOnStream is less than 1, indicating that + * the stream was written by JDK 1.1, + * set the int fields such as maximumIntegerDigits + * to be equal to the byte fields such as maxIntegerDigits, + * since the int fields were not present in JDK 1.1. + * Finally, set serialVersionOnStream back to the maximum allowed value so that + * default serialization will work properly if this object is streamed out again. + * + *

If minimumIntegerDigits is greater than + * maximumIntegerDigits or minimumFractionDigits + * is greater than maximumFractionDigits, then the stream data + * is invalid and this method throws an InvalidObjectException. + * In addition, if any of these values is negative, then this method throws + * an InvalidObjectException. + * + * @since 1.2 + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) { + // Didn't have additional int fields, reassign to use them. + maximumIntegerDigits = maxIntegerDigits; + minimumIntegerDigits = minIntegerDigits; + maximumFractionDigits = maxFractionDigits; + minimumFractionDigits = minFractionDigits; + } + if (minimumIntegerDigits > maximumIntegerDigits || + minimumFractionDigits > maximumFractionDigits || + minimumIntegerDigits < 0 || minimumFractionDigits < 0) { + throw new InvalidObjectException("Digit count range invalid"); + } + serialVersionOnStream = currentSerialVersion; + } + + /** + * Write out the default serializable data, after first setting + * the byte fields such as maxIntegerDigits to be + * equal to the int fields such as maximumIntegerDigits + * (or to Byte.MAX_VALUE, whichever is smaller), for compatibility + * with the JDK 1.1 version of the stream format. + * + * @since 1.2 + */ + private void writeObject(ObjectOutputStream stream) + throws IOException + { + maxIntegerDigits = (maximumIntegerDigits > Byte.MAX_VALUE) ? + Byte.MAX_VALUE : (byte)maximumIntegerDigits; + minIntegerDigits = (minimumIntegerDigits > Byte.MAX_VALUE) ? + Byte.MAX_VALUE : (byte)minimumIntegerDigits; + maxFractionDigits = (maximumFractionDigits > Byte.MAX_VALUE) ? + Byte.MAX_VALUE : (byte)maximumFractionDigits; + minFractionDigits = (minimumFractionDigits > Byte.MAX_VALUE) ? + Byte.MAX_VALUE : (byte)minimumFractionDigits; + stream.defaultWriteObject(); + } + + /** + * Cache to hold the NumberPatterns of a Locale. + */ + private static final Hashtable cachedLocaleData = new Hashtable(3); + + // Constants used by factory methods to specify a style of format. + private static final int NUMBERSTYLE = 0; + private static final int CURRENCYSTYLE = 1; + private static final int PERCENTSTYLE = 2; + private static final int SCIENTIFICSTYLE = 3; + private static final int INTEGERSTYLE = 4; + + /** + * True if the grouping (i.e. thousands) separator is used when + * formatting and parsing numbers. + * + * @serial + * @see #isGroupingUsed + */ + private boolean groupingUsed = true; + + /** + * The maximum number of digits allowed in the integer portion of a + * number. maxIntegerDigits must be greater than or equal to + * minIntegerDigits. + *

+ * Note: This field exists only for serialization + * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new + * int field maximumIntegerDigits is used instead. + * When writing to a stream, maxIntegerDigits is set to + * maximumIntegerDigits or Byte.MAX_VALUE, + * whichever is smaller. When reading from a stream, this field is used + * only if serialVersionOnStream is less than 1. + * + * @serial + * @see #getMaximumIntegerDigits + */ + private byte maxIntegerDigits = 40; + + /** + * The minimum number of digits allowed in the integer portion of a + * number. minimumIntegerDigits must be less than or equal to + * maximumIntegerDigits. + *

+ * Note: This field exists only for serialization + * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new + * int field minimumIntegerDigits is used instead. + * When writing to a stream, minIntegerDigits is set to + * minimumIntegerDigits or Byte.MAX_VALUE, + * whichever is smaller. When reading from a stream, this field is used + * only if serialVersionOnStream is less than 1. + * + * @serial + * @see #getMinimumIntegerDigits + */ + private byte minIntegerDigits = 1; + + /** + * The maximum number of digits allowed in the fractional portion of a + * number. maximumFractionDigits must be greater than or equal to + * minimumFractionDigits. + *

+ * Note: This field exists only for serialization + * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new + * int field maximumFractionDigits is used instead. + * When writing to a stream, maxFractionDigits is set to + * maximumFractionDigits or Byte.MAX_VALUE, + * whichever is smaller. When reading from a stream, this field is used + * only if serialVersionOnStream is less than 1. + * + * @serial + * @see #getMaximumFractionDigits + */ + private byte maxFractionDigits = 3; // invariant, >= minFractionDigits + + /** + * The minimum number of digits allowed in the fractional portion of a + * number. minimumFractionDigits must be less than or equal to + * maximumFractionDigits. + *

+ * Note: This field exists only for serialization + * compatibility with JDK 1.1. In Java platform 2 v1.2 and higher, the new + * int field minimumFractionDigits is used instead. + * When writing to a stream, minFractionDigits is set to + * minimumFractionDigits or Byte.MAX_VALUE, + * whichever is smaller. When reading from a stream, this field is used + * only if serialVersionOnStream is less than 1. + * + * @serial + * @see #getMinimumFractionDigits + */ + private byte minFractionDigits = 0; + + /** + * True if this format will parse numbers as integers only. + * + * @serial + * @see #isParseIntegerOnly + */ + private boolean parseIntegerOnly = false; + + // new fields for 1.2. byte is too small for integer digits. + + /** + * The maximum number of digits allowed in the integer portion of a + * number. maximumIntegerDigits must be greater than or equal to + * minimumIntegerDigits. + * + * @serial + * @since 1.2 + * @see #getMaximumIntegerDigits + */ + private int maximumIntegerDigits = 40; + + /** + * The minimum number of digits allowed in the integer portion of a + * number. minimumIntegerDigits must be less than or equal to + * maximumIntegerDigits. + * + * @serial + * @since 1.2 + * @see #getMinimumIntegerDigits + */ + private int minimumIntegerDigits = 1; + + /** + * The maximum number of digits allowed in the fractional portion of a + * number. maximumFractionDigits must be greater than or equal to + * minimumFractionDigits. + * + * @serial + * @since 1.2 + * @see #getMaximumFractionDigits + */ + private int maximumFractionDigits = 3; // invariant, >= minFractionDigits + + /** + * The minimum number of digits allowed in the fractional portion of a + * number. minimumFractionDigits must be less than or equal to + * maximumFractionDigits. + * + * @serial + * @since 1.2 + * @see #getMinimumFractionDigits + */ + private int minimumFractionDigits = 0; + + static final int currentSerialVersion = 1; + + /** + * Describes the version of NumberFormat present on the stream. + * Possible values are: + *

    + *
  • 0 (or uninitialized): the JDK 1.1 version of the stream format. + * In this version, the int fields such as + * maximumIntegerDigits were not present, and the byte + * fields such as maxIntegerDigits are used instead. + * + *
  • 1: the 1.2 version of the stream format. The values of the + * byte fields such as maxIntegerDigits are ignored, + * and the int fields such as maximumIntegerDigits + * are used instead. + *
+ * When streaming out a NumberFormat, the most recent format + * (corresponding to the highest allowable serialVersionOnStream) + * is always written. + * + * @serial + * @since 1.2 + */ + private int serialVersionOnStream = currentSerialVersion; + + // Removed "implements Cloneable" clause. Needs to update serialization + // ID for backward compatibility. + static final long serialVersionUID = -2308460125733713944L; + + + // + // class for AttributedCharacterIterator attributes + // + /** + * Defines constants that are used as attribute keys in the + * AttributedCharacterIterator returned + * from NumberFormat.formatToCharacterIterator and as + * field identifiers in FieldPosition. + * + * @since 1.4 + */ + public static class Field extends Format.Field { + + // Proclaim serial compatibility with 1.4 FCS + private static final long serialVersionUID = 7494728892700160890L; + + // table of all instances in this class, used by readResolve + private static final Map instanceMap = new HashMap(11); + + /** + * Creates a Field instance with the specified + * name. + * + * @param name Name of the attribute + */ + protected Field(String name) { + super(name); + if (this.getClass() == NumberFormat.Field.class) { + instanceMap.put(name, this); + } + } + + /** + * Resolves instances being deserialized to the predefined constants. + * + * @throws InvalidObjectException if the constant could not be resolved. + * @return resolved NumberFormat.Field constant + */ + protected Object readResolve() throws InvalidObjectException { + if (this.getClass() != NumberFormat.Field.class) { + throw new InvalidObjectException("subclass didn't correctly implement readResolve"); + } + + Object instance = instanceMap.get(getName()); + if (instance != null) { + return instance; + } else { + throw new InvalidObjectException("unknown attribute name"); + } + } + + /** + * Constant identifying the integer field. + */ + public static final Field INTEGER = new Field("integer"); + + /** + * Constant identifying the fraction field. + */ + public static final Field FRACTION = new Field("fraction"); + + /** + * Constant identifying the exponent field. + */ + public static final Field EXPONENT = new Field("exponent"); + + /** + * Constant identifying the decimal separator field. + */ + public static final Field DECIMAL_SEPARATOR = + new Field("decimal separator"); + + /** + * Constant identifying the sign field. + */ + public static final Field SIGN = new Field("sign"); + + /** + * Constant identifying the grouping separator field. + */ + public static final Field GROUPING_SEPARATOR = + new Field("grouping separator"); + + /** + * Constant identifying the exponent symbol field. + */ + public static final Field EXPONENT_SYMBOL = new + Field("exponent symbol"); + + /** + * Constant identifying the percent field. + */ + public static final Field PERCENT = new Field("percent"); + + /** + * Constant identifying the permille field. + */ + public static final Field PERMILLE = new Field("per mille"); + + /** + * Constant identifying the currency field. + */ + public static final Field CURRENCY = new Field("currency"); + + /** + * Constant identifying the exponent sign field. + */ + public static final Field EXPONENT_SIGN = new Field("exponent sign"); + } + + /** + * Obtains a NumberFormat instance from a NumberFormatProvider implementation. + */ + private static class NumberFormatGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final NumberFormatGetter INSTANCE = new NumberFormatGetter(); + + public NumberFormat getObject(NumberFormatProvider numberFormatProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 1; + int choice = (Integer)params[0]; + + switch (choice) { + case NUMBERSTYLE: + return numberFormatProvider.getNumberInstance(locale); + case PERCENTSTYLE: + return numberFormatProvider.getPercentInstance(locale); + case CURRENCYSTYLE: + return numberFormatProvider.getCurrencyInstance(locale); + case INTEGERSTYLE: + return numberFormatProvider.getIntegerInstance(locale); + default: + assert false : choice; + } + + return null; + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/ParseException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/ParseException.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 1996, 1998, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +/** + * Signals that an error has been reached unexpectedly + * while parsing. + * @see java.lang.Exception + * @see java.text.Format + * @see java.text.FieldPosition + * @author Mark Davis + */ +public +class ParseException extends Exception { + + /** + * Constructs a ParseException with the specified detail message and + * offset. + * A detail message is a String that describes this particular exception. + * @param s the detail message + * @param errorOffset the position where the error is found while parsing. + */ + public ParseException(String s, int errorOffset) { + super(s); + this.errorOffset = errorOffset; + } + + /** + * Returns the position where the error was found. + */ + public int getErrorOffset () { + return errorOffset; + } + + //============ privates ============ + /** + * The zero-based character offset into the string being parsed at which + * the error was found during parsing. + * @serial + */ + private int errorOffset; +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/ParsePosition.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/ParsePosition.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 1996, 2002, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + + +/** + * ParsePosition is a simple class used by Format + * and its subclasses to keep track of the current position during parsing. + * The parseObject method in the various Format + * classes requires a ParsePosition object as an argument. + * + *

+ * By design, as you parse through a string with different formats, + * you can use the same ParsePosition, since the index parameter + * records the current position. + * + * @author Mark Davis + * @see java.text.Format + */ + +public class ParsePosition { + + /** + * Input: the place you start parsing. + *
Output: position where the parse stopped. + * This is designed to be used serially, + * with each call setting index up for the next one. + */ + int index = 0; + int errorIndex = -1; + + /** + * Retrieve the current parse position. On input to a parse method, this + * is the index of the character at which parsing will begin; on output, it + * is the index of the character following the last character parsed. + */ + public int getIndex() { + return index; + } + + /** + * Set the current parse position. + */ + public void setIndex(int index) { + this.index = index; + } + + /** + * Create a new ParsePosition with the given initial index. + */ + public ParsePosition(int index) { + this.index = index; + } + /** + * Set the index at which a parse error occurred. Formatters + * should set this before returning an error code from their + * parseObject method. The default value is -1 if this is not set. + * @since 1.2 + */ + public void setErrorIndex(int ei) + { + errorIndex = ei; + } + + /** + * Retrieve the index at which an error occurred, or -1 if the + * error index has not been set. + * @since 1.2 + */ + public int getErrorIndex() + { + return errorIndex; + } + /** + * Overrides equals + */ + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof ParsePosition)) + return false; + ParsePosition other = (ParsePosition) obj; + return (index == other.index && errorIndex == other.errorIndex); + } + + /** + * Returns a hash code for this ParsePosition. + * @return a hash code value for this object + */ + public int hashCode() { + return (errorIndex << 16) | index; + } + + /** + * Return a string representation of this ParsePosition. + * @return a string representation of this object + */ + public String toString() { + return getClass().getName() + + "[index=" + index + + ",errorIndex=" + errorIndex + ']'; + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/text/SimpleDateFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/text/SimpleDateFormat.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,2350 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.SimpleTimeZone; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import sun.util.calendar.CalendarUtils; +import sun.util.calendar.ZoneInfoFile; +import sun.util.resources.LocaleData; + +import static java.text.DateFormatSymbols.*; + +/** + * SimpleDateFormat is a concrete class for formatting and + * parsing dates in a locale-sensitive manner. It allows for formatting + * (date -> text), parsing (text -> date), and normalization. + * + *

+ * SimpleDateFormat allows you to start by choosing + * any user-defined patterns for date-time formatting. However, you + * are encouraged to create a date-time formatter with either + * getTimeInstance, getDateInstance, or + * getDateTimeInstance in DateFormat. Each + * of these class methods can return a date/time formatter initialized + * with a default format pattern. You may modify the format pattern + * using the applyPattern methods as desired. + * For more information on using these methods, see + * {@link DateFormat}. + * + *

Date and Time Patterns

+ *

+ * Date and time formats are specified by date and time pattern + * strings. + * Within date and time pattern strings, unquoted letters from + * 'A' to 'Z' and from 'a' to + * 'z' are interpreted as pattern letters representing the + * components of a date or time string. + * Text can be quoted using single quotes (') to avoid + * interpretation. + * "''" represents a single quote. + * All other characters are not interpreted; they're simply copied into the + * output string during formatting or matched against the input string + * during parsing. + *

+ * The following pattern letters are defined (all other characters from + * 'A' to 'Z' and from 'a' to + * 'z' are reserved): + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Letter + * Date or Time Component + * Presentation + * Examples + *
G + * Era designator + * Text + * AD + *
y + * Year + * Year + * 1996; 96 + *
Y + * Week year + * Year + * 2009; 09 + *
M + * Month in year + * Month + * July; Jul; 07 + *
w + * Week in year + * Number + * 27 + *
W + * Week in month + * Number + * 2 + *
D + * Day in year + * Number + * 189 + *
d + * Day in month + * Number + * 10 + *
F + * Day of week in month + * Number + * 2 + *
E + * Day name in week + * Text + * Tuesday; Tue + *
u + * Day number of week (1 = Monday, ..., 7 = Sunday) + * Number + * 1 + *
a + * Am/pm marker + * Text + * PM + *
H + * Hour in day (0-23) + * Number + * 0 + *
k + * Hour in day (1-24) + * Number + * 24 + *
K + * Hour in am/pm (0-11) + * Number + * 0 + *
h + * Hour in am/pm (1-12) + * Number + * 12 + *
m + * Minute in hour + * Number + * 30 + *
s + * Second in minute + * Number + * 55 + *
S + * Millisecond + * Number + * 978 + *
z + * Time zone + * General time zone + * Pacific Standard Time; PST; GMT-08:00 + *
Z + * Time zone + * RFC 822 time zone + * -0800 + *
X + * Time zone + * ISO 8601 time zone + * -08; -0800; -08:00 + *
+ *
+ * Pattern letters are usually repeated, as their number determines the + * exact presentation: + *
    + *
  • Text: + * For formatting, if the number of pattern letters is 4 or more, + * the full form is used; otherwise a short or abbreviated form + * is used if available. + * For parsing, both forms are accepted, independent of the number + * of pattern letters.

  • + *
  • Number: + * For formatting, the number of pattern letters is the minimum + * number of digits, and shorter numbers are zero-padded to this amount. + * For parsing, the number of pattern letters is ignored unless + * it's needed to separate two adjacent fields.

  • + *
  • Year: + * If the formatter's {@link #getCalendar() Calendar} is the Gregorian + * calendar, the following rules are applied.
    + *
      + *
    • For formatting, if the number of pattern letters is 2, the year + * is truncated to 2 digits; otherwise it is interpreted as a + * number. + *
    • For parsing, if the number of pattern letters is more than 2, + * the year is interpreted literally, regardless of the number of + * digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to + * Jan 11, 12 A.D. + *
    • For parsing with the abbreviated year pattern ("y" or "yy"), + * SimpleDateFormat must interpret the abbreviated year + * relative to some century. It does this by adjusting dates to be + * within 80 years before and 20 years after the time the SimpleDateFormat + * instance is created. For example, using a pattern of "MM/dd/yy" and a + * SimpleDateFormat instance created on Jan 1, 1997, the string + * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64" + * would be interpreted as May 4, 1964. + * During parsing, only strings consisting of exactly two digits, as defined by + * {@link Character#isDigit(char)}, will be parsed into the default century. + * Any other numeric string, such as a one digit string, a three or more digit + * string, or a two digit string that isn't all digits (for example, "-1"), is + * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the + * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC. + *
    + * Otherwise, calendar system specific forms are applied. + * For both formatting and parsing, if the number of pattern + * letters is 4 or more, a calendar specific {@linkplain + * Calendar#LONG long form} is used. Otherwise, a calendar + * specific {@linkplain Calendar#SHORT short or abbreviated form} + * is used.
    + *
    + * If week year {@code 'Y'} is specified and the {@linkplain + * #getCalendar() calendar} doesn't support any week + * years, the calendar year ({@code 'y'}) is used instead. The + * support of week years can be tested with a call to {@link + * DateFormat#getCalendar() getCalendar()}.{@link + * java.util.Calendar#isWeekDateSupported() + * isWeekDateSupported()}.

  • + *
  • Month: + * If the number of pattern letters is 3 or more, the month is + * interpreted as text; otherwise, + * it is interpreted as a number.

  • + *
  • General time zone: + * Time zones are interpreted as text if they have + * names. For time zones representing a GMT offset value, the + * following syntax is used: + *
    + *     GMTOffsetTimeZone:
    + *             GMT Sign Hours : Minutes
    + *     Sign: one of
    + *             + -
    + *     Hours:
    + *             Digit
    + *             Digit Digit
    + *     Minutes:
    + *             Digit Digit
    + *     Digit: one of
    + *             0 1 2 3 4 5 6 7 8 9
    + * Hours must be between 0 and 23, and Minutes must be between + * 00 and 59. The format is locale independent and digits must be taken + * from the Basic Latin block of the Unicode standard. + *

    For parsing, RFC 822 time zones are also + * accepted.

  • + *
  • RFC 822 time zone: + * For formatting, the RFC 822 4-digit time zone format is used: + * + *
    + *     RFC822TimeZone:
    + *             Sign TwoDigitHours Minutes
    + *     TwoDigitHours:
    + *             Digit Digit
    + * TwoDigitHours must be between 00 and 23. Other definitions + * are as for general time zones. + * + *

    For parsing, general time zones are also + * accepted. + *

  • ISO 8601 Time zone: + * The number of pattern letters designates the format for both formatting + * and parsing as follows: + *
    + *     ISO8601TimeZone:
    + *             OneLetterISO8601TimeZone
    + *             TwoLetterISO8601TimeZone
    + *             ThreeLetterISO8601TimeZone
    + *     OneLetterISO8601TimeZone:
    + *             Sign TwoDigitHours
    + *             {@code Z}
    + *     TwoLetterISO8601TimeZone:
    + *             Sign TwoDigitHours Minutes
    + *             {@code Z}
    + *     ThreeLetterISO8601TimeZone:
    + *             Sign TwoDigitHours {@code :} Minutes
    + *             {@code Z}
    + * Other definitions are as for general time zones or + * RFC 822 time zones. + * + *

    For formatting, if the offset value from GMT is 0, {@code "Z"} is + * produced. If the number of pattern letters is 1, any fraction of an hour + * is ignored. For example, if the pattern is {@code "X"} and the time zone is + * {@code "GMT+05:30"}, {@code "+05"} is produced. + * + *

    For parsing, {@code "Z"} is parsed as the UTC time zone designator. + * General time zones are not accepted. + * + *

    If the number of pattern letters is 4 or more, {@link + * IllegalArgumentException} is thrown when constructing a {@code + * SimpleDateFormat} or {@linkplain #applyPattern(String) applying a + * pattern}. + *

+ * SimpleDateFormat also supports localized date and time + * pattern strings. In these strings, the pattern letters described above + * may be replaced with other, locale dependent, pattern letters. + * SimpleDateFormat does not deal with the localization of text + * other than the pattern letters; that's up to the client of the class. + *

+ * + *

Examples

+ * + * The following examples show how date and time patterns are interpreted in + * the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time + * in the U.S. Pacific Time time zone. + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
Date and Time Pattern + * Result + *
"yyyy.MM.dd G 'at' HH:mm:ss z" + * 2001.07.04 AD at 12:08:56 PDT + *
"EEE, MMM d, ''yy" + * Wed, Jul 4, '01 + *
"h:mm a" + * 12:08 PM + *
"hh 'o''clock' a, zzzz" + * 12 o'clock PM, Pacific Daylight Time + *
"K:mm a, z" + * 0:08 PM, PDT + *
"yyyyy.MMMMM.dd GGG hh:mm aaa" + * 02001.July.04 AD 12:08 PM + *
"EEE, d MMM yyyy HH:mm:ss Z" + * Wed, 4 Jul 2001 12:08:56 -0700 + *
"yyMMddHHmmssZ" + * 010704120856-0700 + *
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" + * 2001-07-04T12:08:56.235-0700 + *
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + * 2001-07-04T12:08:56.235-07:00 + *
"YYYY-'W'ww-u" + * 2001-W27-3 + *
+ *
+ * + *

Synchronization

+ * + *

+ * Date formats are not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + * @see Java Tutorial + * @see java.util.Calendar + * @see java.util.TimeZone + * @see DateFormat + * @see DateFormatSymbols + * @author Mark Davis, Chen-Lieh Huang, Alan Liu + */ +public class SimpleDateFormat extends DateFormat { + + // the official serial version ID which says cryptically + // which version we're compatible with + static final long serialVersionUID = 4774881970558875024L; + + // the internal serial version which says which version was written + // - 0 (default) for version up to JDK 1.1.3 + // - 1 for version from JDK 1.1.4, which includes a new field + static final int currentSerialVersion = 1; + + /** + * The version of the serialized data on the stream. Possible values: + *

    + *
  • 0 or not present on stream: JDK 1.1.3. This version + * has no defaultCenturyStart on stream. + *
  • 1 JDK 1.1.4 or later. This version adds + * defaultCenturyStart. + *
+ * When streaming out this class, the most recent format + * and the highest allowable serialVersionOnStream + * is written. + * @serial + * @since JDK1.1.4 + */ + private int serialVersionOnStream = currentSerialVersion; + + /** + * The pattern string of this formatter. This is always a non-localized + * pattern. May not be null. See class documentation for details. + * @serial + */ + private String pattern; + + /** + * Saved numberFormat and pattern. + * @see SimpleDateFormat#checkNegativeNumberExpression + */ + transient private NumberFormat originalNumberFormat; + transient private String originalNumberPattern; + + /** + * The minus sign to be used with format and parse. + */ + transient private char minusSign = '-'; + + /** + * True when a negative sign follows a number. + * (True as default in Arabic.) + */ + transient private boolean hasFollowingMinusSign = false; + + /** + * The compiled pattern. + */ + transient private char[] compiledPattern; + + /** + * Tags for the compiled pattern. + */ + private final static int TAG_QUOTE_ASCII_CHAR = 100; + private final static int TAG_QUOTE_CHARS = 101; + + /** + * Locale dependent digit zero. + * @see #zeroPaddingNumber + * @see java.text.DecimalFormatSymbols#getZeroDigit + */ + transient private char zeroDigit; + + /** + * The symbols used by this formatter for week names, month names, + * etc. May not be null. + * @serial + * @see java.text.DateFormatSymbols + */ + private DateFormatSymbols formatData; + + /** + * We map dates with two-digit years into the century starting at + * defaultCenturyStart, which may be any date. May + * not be null. + * @serial + * @since JDK1.1.4 + */ + private Date defaultCenturyStart; + + transient private int defaultCenturyStartYear; + + private static final int MILLIS_PER_MINUTE = 60 * 1000; + + // For time zones that have no names, use strings GMT+minutes and + // GMT-minutes. For instance, in France the time zone is GMT+60. + private static final String GMT = "GMT"; + + /** + * Cache to hold the DateTimePatterns of a Locale. + */ + private static final ConcurrentMap cachedLocaleData + = new ConcurrentHashMap(3); + + /** + * Cache NumberFormat instances with Locale key. + */ + private static final ConcurrentMap cachedNumberFormatData + = new ConcurrentHashMap(3); + + /** + * The Locale used to instantiate this + * SimpleDateFormat. The value may be null if this object + * has been created by an older SimpleDateFormat and + * deserialized. + * + * @serial + * @since 1.6 + */ + private Locale locale; + + /** + * Indicates whether this SimpleDateFormat should use + * the DateFormatSymbols. If true, the format and parse methods + * use the DateFormatSymbols values. If false, the format and + * parse methods call Calendar.getDisplayName or + * Calendar.getDisplayNames. + */ + transient boolean useDateFormatSymbols; + + /** + * Constructs a SimpleDateFormat using the default pattern and + * date format symbols for the default locale. + * Note: This constructor may not support all locales. + * For full coverage, use the factory methods in the {@link DateFormat} + * class. + */ + public SimpleDateFormat() { + this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Constructs a SimpleDateFormat using the given pattern and + * the default date format symbols for the default locale. + * Note: This constructor may not support all locales. + * For full coverage, use the factory methods in the {@link DateFormat} + * class. + * + * @param pattern the pattern describing the date and time format + * @exception NullPointerException if the given pattern is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public SimpleDateFormat(String pattern) + { + this(pattern, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Constructs a SimpleDateFormat using the given pattern and + * the default date format symbols for the given locale. + * Note: This constructor may not support all locales. + * For full coverage, use the factory methods in the {@link DateFormat} + * class. + * + * @param pattern the pattern describing the date and time format + * @param locale the locale whose date format symbols should be used + * @exception NullPointerException if the given pattern or locale is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public SimpleDateFormat(String pattern, Locale locale) + { + if (pattern == null || locale == null) { + throw new NullPointerException(); + } + + initializeCalendar(locale); + this.pattern = pattern; + this.formatData = DateFormatSymbols.getInstanceRef(locale); + this.locale = locale; + initialize(locale); + } + + /** + * Constructs a SimpleDateFormat using the given pattern and + * date format symbols. + * + * @param pattern the pattern describing the date and time format + * @param formatSymbols the date format symbols to be used for formatting + * @exception NullPointerException if the given pattern or formatSymbols is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) + { + if (pattern == null || formatSymbols == null) { + throw new NullPointerException(); + } + + this.pattern = pattern; + this.formatData = (DateFormatSymbols) formatSymbols.clone(); + this.locale = Locale.getDefault(Locale.Category.FORMAT); + initializeCalendar(this.locale); + initialize(this.locale); + useDateFormatSymbols = true; + } + + /* Package-private, called by DateFormat factory methods */ + SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) { + if (loc == null) { + throw new NullPointerException(); + } + + this.locale = loc; + // initialize calendar and related fields + initializeCalendar(loc); + + /* try the cache first */ + String[] dateTimePatterns = cachedLocaleData.get(loc); + if (dateTimePatterns == null) { /* cache miss */ + ResourceBundle r = LocaleData.getDateFormatData(loc); + if (!isGregorianCalendar()) { + try { + dateTimePatterns = r.getStringArray(getCalendarName() + ".DateTimePatterns"); + } catch (MissingResourceException e) { + } + } + if (dateTimePatterns == null) { + dateTimePatterns = r.getStringArray("DateTimePatterns"); + } + /* update cache */ + cachedLocaleData.putIfAbsent(loc, dateTimePatterns); + } + formatData = DateFormatSymbols.getInstanceRef(loc); + if ((timeStyle >= 0) && (dateStyle >= 0)) { + Object[] dateTimeArgs = {dateTimePatterns[timeStyle], + dateTimePatterns[dateStyle + 4]}; + pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs); + } + else if (timeStyle >= 0) { + pattern = dateTimePatterns[timeStyle]; + } + else if (dateStyle >= 0) { + pattern = dateTimePatterns[dateStyle + 4]; + } + else { + throw new IllegalArgumentException("No date or time style specified"); + } + + initialize(loc); + } + + /* Initialize compiledPattern and numberFormat fields */ + private void initialize(Locale loc) { + // Verify and compile the given pattern. + compiledPattern = compile(pattern); + + /* try the cache first */ + numberFormat = cachedNumberFormatData.get(loc); + if (numberFormat == null) { /* cache miss */ + numberFormat = NumberFormat.getIntegerInstance(loc); + numberFormat.setGroupingUsed(false); + + /* update cache */ + cachedNumberFormatData.putIfAbsent(loc, numberFormat); + } + numberFormat = (NumberFormat) numberFormat.clone(); + + initializeDefaultCentury(); + } + + private void initializeCalendar(Locale loc) { + if (calendar == null) { + assert loc != null; + // The format object must be constructed using the symbols for this zone. + // However, the calendar should use the current default TimeZone. + // If this is not contained in the locale zone strings, then the zone + // will be formatted using generic GMT+/-H:MM nomenclature. + calendar = Calendar.getInstance(TimeZone.getDefault(), loc); + } + } + + /** + * Returns the compiled form of the given pattern. The syntax of + * the compiled pattern is: + *
+ * CompiledPattern: + * EntryList + * EntryList: + * Entry + * EntryList Entry + * Entry: + * TagField + * TagField data + * TagField: + * Tag Length + * TaggedData + * Tag: + * pattern_char_index + * TAG_QUOTE_CHARS + * Length: + * short_length + * long_length + * TaggedData: + * TAG_QUOTE_ASCII_CHAR ascii_char + * + *
+ * + * where `short_length' is an 8-bit unsigned integer between 0 and + * 254. `long_length' is a sequence of an 8-bit integer 255 and a + * 32-bit signed integer value which is split into upper and lower + * 16-bit fields in two char's. `pattern_char_index' is an 8-bit + * integer between 0 and 18. `ascii_char' is an 7-bit ASCII + * character value. `data' depends on its Tag value. + *

+ * If Length is short_length, Tag and short_length are packed in a + * single char, as illustrated below. + *

+ * char[0] = (Tag << 8) | short_length; + *
+ * + * If Length is long_length, Tag and 255 are packed in the first + * char and a 32-bit integer, as illustrated below. + *
+ * char[0] = (Tag << 8) | 255; + * char[1] = (char) (long_length >>> 16); + * char[2] = (char) (long_length & 0xffff); + *
+ *

+ * If Tag is a pattern_char_index, its Length is the number of + * pattern characters. For example, if the given pattern is + * "yyyy", Tag is 1 and Length is 4, followed by no data. + *

+ * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's + * following the TagField. For example, if the given pattern is + * "'o''clock'", Length is 7 followed by a char sequence of + * o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k. + *

+ * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII + * character in place of Length. For example, if the given pattern + * is "'o'", the TaggedData entry is + * ((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o'). + * + * @exception NullPointerException if the given pattern is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + private char[] compile(String pattern) { + int length = pattern.length(); + boolean inQuote = false; + StringBuilder compiledPattern = new StringBuilder(length * 2); + StringBuilder tmpBuffer = null; + int count = 0; + int lastTag = -1; + + for (int i = 0; i < length; i++) { + char c = pattern.charAt(i); + + if (c == '\'') { + // '' is treated as a single quote regardless of being + // in a quoted section. + if ((i + 1) < length) { + c = pattern.charAt(i + 1); + if (c == '\'') { + i++; + if (count != 0) { + encode(lastTag, count, compiledPattern); + lastTag = -1; + count = 0; + } + if (inQuote) { + tmpBuffer.append(c); + } else { + compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); + } + continue; + } + } + if (!inQuote) { + if (count != 0) { + encode(lastTag, count, compiledPattern); + lastTag = -1; + count = 0; + } + if (tmpBuffer == null) { + tmpBuffer = new StringBuilder(length); + } else { + tmpBuffer.setLength(0); + } + inQuote = true; + } else { + int len = tmpBuffer.length(); + if (len == 1) { + char ch = tmpBuffer.charAt(0); + if (ch < 128) { + compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch)); + } else { + compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | 1)); + compiledPattern.append(ch); + } + } else { + encode(TAG_QUOTE_CHARS, len, compiledPattern); + compiledPattern.append(tmpBuffer); + } + inQuote = false; + } + continue; + } + if (inQuote) { + tmpBuffer.append(c); + continue; + } + if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) { + if (count != 0) { + encode(lastTag, count, compiledPattern); + lastTag = -1; + count = 0; + } + if (c < 128) { + // In most cases, c would be a delimiter, such as ':'. + compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c)); + } else { + // Take any contiguous non-ASCII alphabet characters and + // put them in a single TAG_QUOTE_CHARS. + int j; + for (j = i + 1; j < length; j++) { + char d = pattern.charAt(j); + if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) { + break; + } + } + compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | (j - i))); + for (; i < j; i++) { + compiledPattern.append(pattern.charAt(i)); + } + i--; + } + continue; + } + + int tag; + if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) { + throw new IllegalArgumentException("Illegal pattern character " + + "'" + c + "'"); + } + if (lastTag == -1 || lastTag == tag) { + lastTag = tag; + count++; + continue; + } + encode(lastTag, count, compiledPattern); + lastTag = tag; + count = 1; + } + + if (inQuote) { + throw new IllegalArgumentException("Unterminated quote"); + } + + if (count != 0) { + encode(lastTag, count, compiledPattern); + } + + // Copy the compiled pattern to a char array + int len = compiledPattern.length(); + char[] r = new char[len]; + compiledPattern.getChars(0, len, r, 0); + return r; + } + + /** + * Encodes the given tag and length and puts encoded char(s) into buffer. + */ + private static final void encode(int tag, int length, StringBuilder buffer) { + if (tag == PATTERN_ISO_ZONE && length >= 4) { + throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length); + } + if (length < 255) { + buffer.append((char)(tag << 8 | length)); + } else { + buffer.append((char)((tag << 8) | 0xff)); + buffer.append((char)(length >>> 16)); + buffer.append((char)(length & 0xffff)); + } + } + + /* Initialize the fields we use to disambiguate ambiguous years. Separate + * so we can call it from readObject(). + */ + private void initializeDefaultCentury() { + calendar.setTimeInMillis(System.currentTimeMillis()); + calendar.add( Calendar.YEAR, -80 ); + parseAmbiguousDatesAsAfter(calendar.getTime()); + } + + /* Define one-century window into which to disambiguate dates using + * two-digit years. + */ + private void parseAmbiguousDatesAsAfter(Date startDate) { + defaultCenturyStart = startDate; + calendar.setTime(startDate); + defaultCenturyStartYear = calendar.get(Calendar.YEAR); + } + + /** + * Sets the 100-year period 2-digit years will be interpreted as being in + * to begin on the date the user specifies. + * + * @param startDate During parsing, two digit years will be placed in the range + * startDate to startDate + 100 years. + * @see #get2DigitYearStart + * @since 1.2 + */ + public void set2DigitYearStart(Date startDate) { + parseAmbiguousDatesAsAfter(new Date(startDate.getTime())); + } + + /** + * Returns the beginning date of the 100-year period 2-digit years are interpreted + * as being within. + * + * @return the start of the 100-year period into which two digit years are + * parsed + * @see #set2DigitYearStart + * @since 1.2 + */ + public Date get2DigitYearStart() { + return (Date) defaultCenturyStart.clone(); + } + + /** + * Formats the given Date into a date/time string and appends + * the result to the given StringBuffer. + * + * @param date the date-time value to be formatted into a date-time string. + * @param toAppendTo where the new date-time text is to be appended. + * @param pos the formatting position. On input: an alignment field, + * if desired. On output: the offsets of the alignment field. + * @return the formatted date-time string. + * @exception NullPointerException if the given {@code date} is {@code null}. + */ + public StringBuffer format(Date date, StringBuffer toAppendTo, + FieldPosition pos) + { + pos.beginIndex = pos.endIndex = 0; + return format(date, toAppendTo, pos.getFieldDelegate()); + } + + // Called from Format after creating a FieldDelegate + private StringBuffer format(Date date, StringBuffer toAppendTo, + FieldDelegate delegate) { + // Convert input date to time field list + calendar.setTime(date); + + boolean useDateFormatSymbols = useDateFormatSymbols(); + + for (int i = 0; i < compiledPattern.length; ) { + int tag = compiledPattern[i] >>> 8; + int count = compiledPattern[i++] & 0xff; + if (count == 255) { + count = compiledPattern[i++] << 16; + count |= compiledPattern[i++]; + } + + switch (tag) { + case TAG_QUOTE_ASCII_CHAR: + toAppendTo.append((char)count); + break; + + case TAG_QUOTE_CHARS: + toAppendTo.append(compiledPattern, i, count); + i += count; + break; + + default: + subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols); + break; + } + } + return toAppendTo; + } + + /** + * Formats an Object producing an AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * Each attribute key of the AttributedCharacterIterator will be of type + * DateFormat.Field, with the corresponding attribute value + * being the same as the attribute key. + * + * @exception NullPointerException if obj is null. + * @exception IllegalArgumentException if the Format cannot format the + * given object, or if the Format's pattern string is invalid. + * @param obj The object to format + * @return AttributedCharacterIterator describing the formatted value. + * @since 1.4 + */ + public AttributedCharacterIterator formatToCharacterIterator(Object obj) { + StringBuffer sb = new StringBuffer(); + CharacterIteratorFieldDelegate delegate = new + CharacterIteratorFieldDelegate(); + + if (obj instanceof Date) { + format((Date)obj, sb, delegate); + } + else if (obj instanceof Number) { + format(new Date(((Number)obj).longValue()), sb, delegate); + } + else if (obj == null) { + throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + } + else { + throw new IllegalArgumentException( + "Cannot format given Object as a Date"); + } + return delegate.getIterator(sb.toString()); + } + + // Map index into pattern character string to Calendar field number + private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = + { + Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE, + Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE, + Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK, + Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH, + Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, + Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET, + Calendar.ZONE_OFFSET, + // Pseudo Calendar fields + CalendarBuilder.WEEK_YEAR, + CalendarBuilder.ISO_DAY_OF_WEEK, + Calendar.ZONE_OFFSET + }; + + // Map index into pattern character string to DateFormat field number + private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = { + DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD, + DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, + DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD, + DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD, + DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, + DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD, + DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD, + DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, + DateFormat.TIMEZONE_FIELD, DateFormat.TIMEZONE_FIELD, + DateFormat.YEAR_FIELD, DateFormat.DAY_OF_WEEK_FIELD, + DateFormat.TIMEZONE_FIELD + }; + + // Maps from DecimalFormatSymbols index to Field constant + private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = { + Field.ERA, Field.YEAR, Field.MONTH, Field.DAY_OF_MONTH, + Field.HOUR_OF_DAY1, Field.HOUR_OF_DAY0, Field.MINUTE, + Field.SECOND, Field.MILLISECOND, Field.DAY_OF_WEEK, + Field.DAY_OF_YEAR, Field.DAY_OF_WEEK_IN_MONTH, + Field.WEEK_OF_YEAR, Field.WEEK_OF_MONTH, + Field.AM_PM, Field.HOUR1, Field.HOUR0, Field.TIME_ZONE, + Field.TIME_ZONE, + Field.YEAR, Field.DAY_OF_WEEK, + Field.TIME_ZONE + }; + + /** + * Private member function that does the real date/time formatting. + */ + private void subFormat(int patternCharIndex, int count, + FieldDelegate delegate, StringBuffer buffer, + boolean useDateFormatSymbols) + { + int maxIntCount = Integer.MAX_VALUE; + String current = null; + int beginOffset = buffer.length(); + + int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; + int value; + if (field == CalendarBuilder.WEEK_YEAR) { + if (calendar.isWeekDateSupported()) { + value = calendar.getWeekYear(); + } else { + // use calendar year 'y' instead + patternCharIndex = PATTERN_YEAR; + field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; + value = calendar.get(field); + } + } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) { + value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK)); + } else { + value = calendar.get(field); + } + + int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; + if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) { + current = calendar.getDisplayName(field, style, locale); + } + + // Note: zeroPaddingNumber() assumes that maxDigits is either + // 2 or maxIntCount. If we make any changes to this, + // zeroPaddingNumber() must be fixed. + + switch (patternCharIndex) { + case PATTERN_ERA: // 'G' + if (useDateFormatSymbols) { + String[] eras = formatData.getEras(); + if (value < eras.length) + current = eras[value]; + } + if (current == null) + current = ""; + break; + + case PATTERN_WEEK_YEAR: // 'Y' + case PATTERN_YEAR: // 'y' + if (calendar instanceof GregorianCalendar) { + if (count != 2) + zeroPaddingNumber(value, count, maxIntCount, buffer); + else // count == 2 + zeroPaddingNumber(value, 2, 2, buffer); // clip 1996 to 96 + } else { + if (current == null) { + zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count, + maxIntCount, buffer); + } + } + break; + + case PATTERN_MONTH: // 'M' + if (useDateFormatSymbols) { + String[] months; + if (count >= 4) { + months = formatData.getMonths(); + current = months[value]; + } else if (count == 3) { + months = formatData.getShortMonths(); + current = months[value]; + } + } else { + if (count < 3) { + current = null; + } + } + if (current == null) { + zeroPaddingNumber(value+1, count, maxIntCount, buffer); + } + break; + + case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 + if (current == null) { + if (value == 0) + zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+1, + count, maxIntCount, buffer); + else + zeroPaddingNumber(value, count, maxIntCount, buffer); + } + break; + + case PATTERN_DAY_OF_WEEK: // 'E' + if (useDateFormatSymbols) { + String[] weekdays; + if (count >= 4) { + weekdays = formatData.getWeekdays(); + current = weekdays[value]; + } else { // count < 4, use abbreviated form if exists + weekdays = formatData.getShortWeekdays(); + current = weekdays[value]; + } + } + break; + + case PATTERN_AM_PM: // 'a' + if (useDateFormatSymbols) { + String[] ampm = formatData.getAmPmStrings(); + current = ampm[value]; + } + break; + + case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM + if (current == null) { + if (value == 0) + zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1, + count, maxIntCount, buffer); + else + zeroPaddingNumber(value, count, maxIntCount, buffer); + } + break; + + case PATTERN_ZONE_NAME: // 'z' + if (current == null) { + if (formatData.locale == null || formatData.isZoneStringsSet) { + int zoneIndex = + formatData.getZoneIndex(calendar.getTimeZone().getID()); + if (zoneIndex == -1) { + value = calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET); + buffer.append(ZoneInfoFile.toCustomID(value)); + } else { + int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3; + if (count < 4) { + // Use the short name + index++; + } + String[][] zoneStrings = formatData.getZoneStringsWrapper(); + buffer.append(zoneStrings[zoneIndex][index]); + } + } else { + TimeZone tz = calendar.getTimeZone(); + boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0); + int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG); + buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale)); + } + } + break; + + case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form) + value = (calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET)) / 60000; + + int width = 4; + if (value >= 0) { + buffer.append('+'); + } else { + width++; + } + + int num = (value / 60) * 100 + (value % 60); + CalendarUtils.sprintf0d(buffer, num, width); + break; + + case PATTERN_ISO_ZONE: // 'X' + value = calendar.get(Calendar.ZONE_OFFSET) + + calendar.get(Calendar.DST_OFFSET); + + if (value == 0) { + buffer.append('Z'); + break; + } + + value /= 60000; + if (value >= 0) { + buffer.append('+'); + } else { + buffer.append('-'); + value = -value; + } + + CalendarUtils.sprintf0d(buffer, value / 60, 2); + if (count == 1) { + break; + } + + if (count == 3) { + buffer.append(':'); + } + CalendarUtils.sprintf0d(buffer, value % 60, 2); + break; + + default: + // case PATTERN_DAY_OF_MONTH: // 'd' + // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 + // case PATTERN_MINUTE: // 'm' + // case PATTERN_SECOND: // 's' + // case PATTERN_MILLISECOND: // 'S' + // case PATTERN_DAY_OF_YEAR: // 'D' + // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' + // case PATTERN_WEEK_OF_YEAR: // 'w' + // case PATTERN_WEEK_OF_MONTH: // 'W' + // case PATTERN_HOUR0: // 'K' eg, 11PM + 1 hour =>> 0 AM + // case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 7 + if (current == null) { + zeroPaddingNumber(value, count, maxIntCount, buffer); + } + break; + } // switch (patternCharIndex) + + if (current != null) { + buffer.append(current); + } + + int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]; + Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex]; + + delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer); + } + + /** + * Formats a number with the specified minimum and maximum number of digits. + */ + private final void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer) + { + // Optimization for 1, 2 and 4 digit numbers. This should + // cover most cases of formatting date/time related items. + // Note: This optimization code assumes that maxDigits is + // either 2 or Integer.MAX_VALUE (maxIntCount in format()). + try { + if (zeroDigit == 0) { + zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit(); + } + if (value >= 0) { + if (value < 100 && minDigits >= 1 && minDigits <= 2) { + if (value < 10) { + if (minDigits == 2) { + buffer.append(zeroDigit); + } + buffer.append((char)(zeroDigit + value)); + } else { + buffer.append((char)(zeroDigit + value / 10)); + buffer.append((char)(zeroDigit + value % 10)); + } + return; + } else if (value >= 1000 && value < 10000) { + if (minDigits == 4) { + buffer.append((char)(zeroDigit + value / 1000)); + value %= 1000; + buffer.append((char)(zeroDigit + value / 100)); + value %= 100; + buffer.append((char)(zeroDigit + value / 10)); + buffer.append((char)(zeroDigit + value % 10)); + return; + } + if (minDigits == 2 && maxDigits == 2) { + zeroPaddingNumber(value % 100, 2, 2, buffer); + return; + } + } + } + } catch (Exception e) { + } + + numberFormat.setMinimumIntegerDigits(minDigits); + numberFormat.setMaximumIntegerDigits(maxDigits); + numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE); + } + + + /** + * Parses text from a string to produce a Date. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * date is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + * + *

This parsing operation uses the {@link DateFormat#calendar + * calendar} to produce a {@code Date}. All of the {@code + * calendar}'s date-time fields are {@linkplain Calendar#clear() + * cleared} before parsing, and the {@code calendar}'s default + * values of the date-time fields are used for any missing + * date-time information. For example, the year value of the + * parsed {@code Date} is 1970 with {@link GregorianCalendar} if + * no year value is given from the parsing operation. The {@code + * TimeZone} value may be overwritten, depending on the given + * pattern and the time zone value in {@code text}. Any {@code + * TimeZone} value that has previously been set by a call to + * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need + * to be restored for further operations. + * + * @param text A String, part of which should be parsed. + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return A Date parsed from the string. In case of + * error, returns null. + * @exception NullPointerException if text or pos is null. + */ + public Date parse(String text, ParsePosition pos) + { + checkNegativeNumberExpression(); + + int start = pos.index; + int oldStart = start; + int textLength = text.length(); + + boolean[] ambiguousYear = {false}; + + CalendarBuilder calb = new CalendarBuilder(); + + for (int i = 0; i < compiledPattern.length; ) { + int tag = compiledPattern[i] >>> 8; + int count = compiledPattern[i++] & 0xff; + if (count == 255) { + count = compiledPattern[i++] << 16; + count |= compiledPattern[i++]; + } + + switch (tag) { + case TAG_QUOTE_ASCII_CHAR: + if (start >= textLength || text.charAt(start) != (char)count) { + pos.index = oldStart; + pos.errorIndex = start; + return null; + } + start++; + break; + + case TAG_QUOTE_CHARS: + while (count-- > 0) { + if (start >= textLength || text.charAt(start) != compiledPattern[i++]) { + pos.index = oldStart; + pos.errorIndex = start; + return null; + } + start++; + } + break; + + default: + // Peek the next pattern to determine if we need to + // obey the number of pattern letters for + // parsing. It's required when parsing contiguous + // digit text (e.g., "20010704") with a pattern which + // has no delimiters between fields, like "yyyyMMdd". + boolean obeyCount = false; + + // In Arabic, a minus sign for a negative number is put after + // the number. Even in another locale, a minus sign can be + // put after a number using DateFormat.setNumberFormat(). + // If both the minus sign and the field-delimiter are '-', + // subParse() needs to determine whether a '-' after a number + // in the given text is a delimiter or is a minus sign for the + // preceding number. We give subParse() a clue based on the + // information in compiledPattern. + boolean useFollowingMinusSignAsDelimiter = false; + + if (i < compiledPattern.length) { + int nextTag = compiledPattern[i] >>> 8; + if (!(nextTag == TAG_QUOTE_ASCII_CHAR || + nextTag == TAG_QUOTE_CHARS)) { + obeyCount = true; + } + + if (hasFollowingMinusSign && + (nextTag == TAG_QUOTE_ASCII_CHAR || + nextTag == TAG_QUOTE_CHARS)) { + int c; + if (nextTag == TAG_QUOTE_ASCII_CHAR) { + c = compiledPattern[i] & 0xff; + } else { + c = compiledPattern[i+1]; + } + + if (c == minusSign) { + useFollowingMinusSignAsDelimiter = true; + } + } + } + start = subParse(text, start, tag, count, obeyCount, + ambiguousYear, pos, + useFollowingMinusSignAsDelimiter, calb); + if (start < 0) { + pos.index = oldStart; + return null; + } + } + } + + // At this point the fields of Calendar have been set. Calendar + // will fill in default values for missing fields when the time + // is computed. + + pos.index = start; + + Date parsedDate; + try { + parsedDate = calb.establish(calendar).getTime(); + // If the year value is ambiguous, + // then the two-digit year == the default start year + if (ambiguousYear[0]) { + if (parsedDate.before(defaultCenturyStart)) { + parsedDate = calb.addYear(100).establish(calendar).getTime(); + } + } + } + // An IllegalArgumentException will be thrown by Calendar.getTime() + // if any fields are out of range, e.g., MONTH == 17. + catch (IllegalArgumentException e) { + pos.errorIndex = start; + pos.index = oldStart; + return null; + } + + return parsedDate; + } + + /** + * Private code-size reduction function used by subParse. + * @param text the time text being parsed. + * @param start where to start parsing. + * @param field the date field being parsed. + * @param data the string array to parsed. + * @return the new start position if matching succeeded; a negative number + * indicating matching failure, otherwise. + */ + private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb) + { + int i = 0; + int count = data.length; + + if (field == Calendar.DAY_OF_WEEK) i = 1; + + // There may be multiple strings in the data[] array which begin with + // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). + // We keep track of the longest match, and return that. Note that this + // unfortunately requires us to test all array elements. + int bestMatchLength = 0, bestMatch = -1; + for (; i bestMatchLength && + text.regionMatches(true, start, data[i], 0, length)) + { + bestMatch = i; + bestMatchLength = length; + } + } + if (bestMatch >= 0) + { + calb.set(field, bestMatch); + return start + bestMatchLength; + } + return -start; + } + + /** + * Performs the same thing as matchString(String, int, int, + * String[]). This method takes a Map instead of + * String[]. + */ + private int matchString(String text, int start, int field, + Map data, CalendarBuilder calb) { + if (data != null) { + String bestMatch = null; + + for (String name : data.keySet()) { + int length = name.length(); + if (bestMatch == null || length > bestMatch.length()) { + if (text.regionMatches(true, start, name, 0, length)) { + bestMatch = name; + } + } + } + + if (bestMatch != null) { + calb.set(field, data.get(bestMatch)); + return start + bestMatch.length(); + } + } + return -start; + } + + private int matchZoneString(String text, int start, String[] zoneNames) { + for (int i = 1; i <= 4; ++i) { + // Checking long and short zones [1 & 2], + // and long and short daylight [3 & 4]. + String zoneName = zoneNames[i]; + if (text.regionMatches(true, start, + zoneName, 0, zoneName.length())) { + return i; + } + } + return -1; + } + + private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex, + String[][] zoneStrings) { + int index = standardIndex + 2; + String zoneName = zoneStrings[zoneIndex][index]; + if (text.regionMatches(true, start, + zoneName, 0, zoneName.length())) { + return true; + } + return false; + } + + /** + * find time zone 'text' matched zoneStrings and set to internal + * calendar. + */ + private int subParseZoneString(String text, int start, CalendarBuilder calb) { + boolean useSameName = false; // true if standard and daylight time use the same abbreviation. + TimeZone currentTimeZone = getTimeZone(); + + // At this point, check for named time zones by looking through + // the locale data from the TimeZoneNames strings. + // Want to be able to parse both short and long forms. + int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID()); + TimeZone tz = null; + String[][] zoneStrings = formatData.getZoneStringsWrapper(); + String[] zoneNames = null; + int nameIndex = 0; + if (zoneIndex != -1) { + zoneNames = zoneStrings[zoneIndex]; + if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { + if (nameIndex <= 2) { + // Check if the standard name (abbr) and the daylight name are the same. + useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); + } + tz = TimeZone.getTimeZone(zoneNames[0]); + } + } + if (tz == null) { + zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID()); + if (zoneIndex != -1) { + zoneNames = zoneStrings[zoneIndex]; + if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { + if (nameIndex <= 2) { + useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); + } + tz = TimeZone.getTimeZone(zoneNames[0]); + } + } + } + + if (tz == null) { + int len = zoneStrings.length; + for (int i = 0; i < len; i++) { + zoneNames = zoneStrings[i]; + if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) { + if (nameIndex <= 2) { + useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]); + } + tz = TimeZone.getTimeZone(zoneNames[0]); + break; + } + } + } + if (tz != null) { // Matched any ? + if (!tz.equals(currentTimeZone)) { + setTimeZone(tz); + } + // If the time zone matched uses the same name + // (abbreviation) for both standard and daylight time, + // let the time zone in the Calendar decide which one. + // + // Also if tz.getDSTSaving() returns 0 for DST, use tz to + // determine the local time. (6645292) + int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0; + if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) { + calb.set(Calendar.ZONE_OFFSET, tz.getRawOffset()) + .set(Calendar.DST_OFFSET, dstAmount); + } + return (start + zoneNames[nameIndex].length()); + } + return 0; + } + + /** + * Parses numeric forms of time zone offset, such as "hh:mm", and + * sets calb to the parsed value. + * + * @param text the text to be parsed + * @param start the character position to start parsing + * @param sign 1: positive; -1: negative + * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's + * @param colon true - colon required between hh and mm; false - no colon required + * @param calb a CalendarBuilder in which the parsed value is stored + * @return updated parsed position, or its negative value to indicate a parsing error + */ + private int subParseNumericZone(String text, int start, int sign, int count, + boolean colon, CalendarBuilder calb) { + int index = start; + + parse: + try { + char c = text.charAt(index++); + // Parse hh + int hours; + if (!isDigit(c)) { + break parse; + } + hours = c - '0'; + c = text.charAt(index++); + if (isDigit(c)) { + hours = hours * 10 + (c - '0'); + } else { + // If no colon in RFC 822 or 'X' (ISO), two digits are + // required. + if (count > 0 || !colon) { + break parse; + } + --index; + } + if (hours > 23) { + break parse; + } + int minutes = 0; + if (count != 1) { + // Proceed with parsing mm + c = text.charAt(index++); + if (colon) { + if (c != ':') { + break parse; + } + c = text.charAt(index++); + } + if (!isDigit(c)) { + break parse; + } + minutes = c - '0'; + c = text.charAt(index++); + if (!isDigit(c)) { + break parse; + } + minutes = minutes * 10 + (c - '0'); + if (minutes > 59) { + break parse; + } + } + minutes += hours * 60; + calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign) + .set(Calendar.DST_OFFSET, 0); + return index; + } catch (IndexOutOfBoundsException e) { + } + return 1 - index; // -(index - 1) + } + + private boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + /** + * Private member function that converts the parsed date strings into + * timeFields. Returns -start (for ParsePosition) if failed. + * @param text the time text to be parsed. + * @param start where to start parsing. + * @param ch the pattern character for the date field text to be parsed. + * @param count the count of a pattern character. + * @param obeyCount if true, then the next field directly abuts this one, + * and we should use the count to know when to stop parsing. + * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] + * is true, then a two-digit year was parsed and may need to be readjusted. + * @param origPos origPos.errorIndex is used to return an error index + * at which a parse error occurred, if matching failure occurs. + * @return the new start position if matching succeeded; -1 indicating + * matching failure, otherwise. In case matching failure occurred, + * an error index is set to origPos.errorIndex. + */ + private int subParse(String text, int start, int patternCharIndex, int count, + boolean obeyCount, boolean[] ambiguousYear, + ParsePosition origPos, + boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) { + Number number = null; + int value = 0; + ParsePosition pos = new ParsePosition(0); + pos.index = start; + if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) { + // use calendar year 'y' instead + patternCharIndex = PATTERN_YEAR; + } + int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; + + // If there are any spaces here, skip over them. If we hit the end + // of the string, then fail. + for (;;) { + if (pos.index >= text.length()) { + origPos.errorIndex = start; + return -1; + } + char c = text.charAt(pos.index); + if (c != ' ' && c != '\t') break; + ++pos.index; + } + + parsing: + { + // We handle a few special cases here where we need to parse + // a number value. We handle further, more generic cases below. We need + // to handle some of them here because some fields require extra processing on + // the parsed value. + if (patternCharIndex == PATTERN_HOUR_OF_DAY1 || + patternCharIndex == PATTERN_HOUR1 || + (patternCharIndex == PATTERN_MONTH && count <= 2) || + patternCharIndex == PATTERN_YEAR || + patternCharIndex == PATTERN_WEEK_YEAR) { + // It would be good to unify this with the obeyCount logic below, + // but that's going to be difficult. + if (obeyCount) { + if ((start+count) > text.length()) { + break parsing; + } + number = numberFormat.parse(text.substring(0, start+count), pos); + } else { + number = numberFormat.parse(text, pos); + } + if (number == null) { + if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) { + break parsing; + } + } else { + value = number.intValue(); + + if (useFollowingMinusSignAsDelimiter && (value < 0) && + (((pos.index < text.length()) && + (text.charAt(pos.index) != minusSign)) || + ((pos.index == text.length()) && + (text.charAt(pos.index-1) == minusSign)))) { + value = -value; + pos.index--; + } + } + } + + boolean useDateFormatSymbols = useDateFormatSymbols(); + + int index; + switch (patternCharIndex) { + case PATTERN_ERA: // 'G' + if (useDateFormatSymbols) { + if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) { + return index; + } + } else { + Map map = calendar.getDisplayNames(field, + Calendar.ALL_STYLES, + locale); + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + break parsing; + + case PATTERN_WEEK_YEAR: // 'Y' + case PATTERN_YEAR: // 'y' + if (!(calendar instanceof GregorianCalendar)) { + // calendar might have text representations for year values, + // such as "\u5143" in JapaneseImperialCalendar. + int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT; + Map map = calendar.getDisplayNames(field, style, locale); + if (map != null) { + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + calb.set(field, value); + return pos.index; + } + + // If there are 3 or more YEAR pattern characters, this indicates + // that the year value is to be treated literally, without any + // two-digit year adjustments (e.g., from "01" to 2001). Otherwise + // we made adjustments to place the 2-digit year in the proper + // century, for parsed strings from "00" to "99". Any other string + // is treated literally: "2250", "-1", "1", "002". + if (count <= 2 && (pos.index - start) == 2 + && Character.isDigit(text.charAt(start)) + && Character.isDigit(text.charAt(start+1))) { + // Assume for example that the defaultCenturyStart is 6/18/1903. + // This means that two-digit years will be forced into the range + // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 + // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond + // to 1904, 1905, etc. If the year is 03, then it is 2003 if the + // other fields specify a date before 6/18, or 1903 if they specify a + // date afterwards. As a result, 03 is an ambiguous year. All other + // two-digit years are unambiguous. + int ambiguousTwoDigitYear = defaultCenturyStartYear % 100; + ambiguousYear[0] = value == ambiguousTwoDigitYear; + value += (defaultCenturyStartYear/100)*100 + + (value < ambiguousTwoDigitYear ? 100 : 0); + } + calb.set(field, value); + return pos.index; + + case PATTERN_MONTH: // 'M' + if (count <= 2) // i.e., M or MM. + { + // Don't want to parse the month if it is a string + // while pattern uses numeric style: M or MM. + // [We computed 'value' above.] + calb.set(Calendar.MONTH, value - 1); + return pos.index; + } + + if (useDateFormatSymbols) { + // count >= 3 // i.e., MMM or MMMM + // Want to be able to parse both short and long forms. + // Try count == 4 first: + int newStart = 0; + if ((newStart = matchString(text, start, Calendar.MONTH, + formatData.getMonths(), calb)) > 0) { + return newStart; + } + // count == 4 failed, now try count == 3 + if ((index = matchString(text, start, Calendar.MONTH, + formatData.getShortMonths(), calb)) > 0) { + return index; + } + } else { + Map map = calendar.getDisplayNames(field, + Calendar.ALL_STYLES, + locale); + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + break parsing; + + case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59 + if (!isLenient()) { + // Validate the hour value in non-lenient + if (value < 1 || value > 24) { + break parsing; + } + } + // [We computed 'value' above.] + if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) + value = 0; + calb.set(Calendar.HOUR_OF_DAY, value); + return pos.index; + + case PATTERN_DAY_OF_WEEK: // 'E' + { + if (useDateFormatSymbols) { + // Want to be able to parse both short and long forms. + // Try count == 4 (DDDD) first: + int newStart = 0; + if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK, + formatData.getWeekdays(), calb)) > 0) { + return newStart; + } + // DDDD failed, now try DDD + if ((index = matchString(text, start, Calendar.DAY_OF_WEEK, + formatData.getShortWeekdays(), calb)) > 0) { + return index; + } + } else { + int[] styles = { Calendar.LONG, Calendar.SHORT }; + for (int style : styles) { + Map map = calendar.getDisplayNames(field, style, locale); + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + } + } + break parsing; + + case PATTERN_AM_PM: // 'a' + if (useDateFormatSymbols) { + if ((index = matchString(text, start, Calendar.AM_PM, + formatData.getAmPmStrings(), calb)) > 0) { + return index; + } + } else { + Map map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale); + if ((index = matchString(text, start, field, map, calb)) > 0) { + return index; + } + } + break parsing; + + case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM + if (!isLenient()) { + // Validate the hour value in non-lenient + if (value < 1 || value > 12) { + break parsing; + } + } + // [We computed 'value' above.] + if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) + value = 0; + calb.set(Calendar.HOUR, value); + return pos.index; + + case PATTERN_ZONE_NAME: // 'z' + case PATTERN_ZONE_VALUE: // 'Z' + { + int sign = 0; + try { + char c = text.charAt(pos.index); + if (c == '+') { + sign = 1; + } else if (c == '-') { + sign = -1; + } + if (sign == 0) { + // Try parsing a custom time zone "GMT+hh:mm" or "GMT". + if ((c == 'G' || c == 'g') + && (text.length() - start) >= GMT.length() + && text.regionMatches(true, start, GMT, 0, GMT.length())) { + pos.index = start + GMT.length(); + + if ((text.length() - pos.index) > 0) { + c = text.charAt(pos.index); + if (c == '+') { + sign = 1; + } else if (c == '-') { + sign = -1; + } + } + + if (sign == 0) { /* "GMT" without offset */ + calb.set(Calendar.ZONE_OFFSET, 0) + .set(Calendar.DST_OFFSET, 0); + return pos.index; + } + + // Parse the rest as "hh:mm" + int i = subParseNumericZone(text, ++pos.index, + sign, 0, true, calb); + if (i > 0) { + return i; + } + pos.index = -i; + } else { + // Try parsing the text as a time zone + // name or abbreviation. + int i = subParseZoneString(text, pos.index, calb); + if (i > 0) { + return i; + } + pos.index = -i; + } + } else { + // Parse the rest as "hhmm" (RFC 822) + int i = subParseNumericZone(text, ++pos.index, + sign, 0, false, calb); + if (i > 0) { + return i; + } + pos.index = -i; + } + } catch (IndexOutOfBoundsException e) { + } + } + break parsing; + + case PATTERN_ISO_ZONE: // 'X' + { + if ((text.length() - pos.index) <= 0) { + break parsing; + } + + int sign = 0; + char c = text.charAt(pos.index); + if (c == 'Z') { + calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0); + return ++pos.index; + } + + // parse text as "+/-hh[[:]mm]" based on count + if (c == '+') { + sign = 1; + } else if (c == '-') { + sign = -1; + } else { + ++pos.index; + break parsing; + } + int i = subParseNumericZone(text, ++pos.index, sign, count, + count == 3, calb); + if (i > 0) { + return i; + } + pos.index = -i; + } + break parsing; + + default: + // case PATTERN_DAY_OF_MONTH: // 'd' + // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59 + // case PATTERN_MINUTE: // 'm' + // case PATTERN_SECOND: // 's' + // case PATTERN_MILLISECOND: // 'S' + // case PATTERN_DAY_OF_YEAR: // 'D' + // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F' + // case PATTERN_WEEK_OF_YEAR: // 'w' + // case PATTERN_WEEK_OF_MONTH: // 'W' + // case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM + // case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field); + + // Handle "generic" fields + if (obeyCount) { + if ((start+count) > text.length()) { + break parsing; + } + number = numberFormat.parse(text.substring(0, start+count), pos); + } else { + number = numberFormat.parse(text, pos); + } + if (number != null) { + value = number.intValue(); + + if (useFollowingMinusSignAsDelimiter && (value < 0) && + (((pos.index < text.length()) && + (text.charAt(pos.index) != minusSign)) || + ((pos.index == text.length()) && + (text.charAt(pos.index-1) == minusSign)))) { + value = -value; + pos.index--; + } + + calb.set(field, value); + return pos.index; + } + break parsing; + } + } + + // Parsing failed. + origPos.errorIndex = pos.index; + return -1; + } + + private final String getCalendarName() { + return calendar.getClass().getName(); + } + + private boolean useDateFormatSymbols() { + if (useDateFormatSymbols) { + return true; + } + return isGregorianCalendar() || locale == null; + } + + private boolean isGregorianCalendar() { + return "java.util.GregorianCalendar".equals(getCalendarName()); + } + + /** + * Translates a pattern, mapping each character in the from string to the + * corresponding character in the to string. + * + * @exception IllegalArgumentException if the given pattern is invalid + */ + private String translatePattern(String pattern, String from, String to) { + StringBuilder result = new StringBuilder(); + boolean inQuote = false; + for (int i = 0; i < pattern.length(); ++i) { + char c = pattern.charAt(i); + if (inQuote) { + if (c == '\'') + inQuote = false; + } + else { + if (c == '\'') + inQuote = true; + else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + int ci = from.indexOf(c); + if (ci >= 0) { + // patternChars is longer than localPatternChars due + // to serialization compatibility. The pattern letters + // unsupported by localPatternChars pass through. + if (ci < to.length()) { + c = to.charAt(ci); + } + } else { + throw new IllegalArgumentException("Illegal pattern " + + " character '" + + c + "'"); + } + } + } + result.append(c); + } + if (inQuote) + throw new IllegalArgumentException("Unfinished quote in pattern"); + return result.toString(); + } + + /** + * Returns a pattern string describing this date format. + * + * @return a pattern string describing this date format. + */ + public String toPattern() { + return pattern; + } + + /** + * Returns a localized pattern string describing this date format. + * + * @return a localized pattern string describing this date format. + */ + public String toLocalizedPattern() { + return translatePattern(pattern, + DateFormatSymbols.patternChars, + formatData.getLocalPatternChars()); + } + + /** + * Applies the given pattern string to this date format. + * + * @param pattern the new date and time pattern for this date format + * @exception NullPointerException if the given pattern is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public void applyPattern(String pattern) + { + compiledPattern = compile(pattern); + this.pattern = pattern; + } + + /** + * Applies the given localized pattern string to this date format. + * + * @param pattern a String to be mapped to the new date and time format + * pattern for this format + * @exception NullPointerException if the given pattern is null + * @exception IllegalArgumentException if the given pattern is invalid + */ + public void applyLocalizedPattern(String pattern) { + String p = translatePattern(pattern, + formatData.getLocalPatternChars(), + DateFormatSymbols.patternChars); + compiledPattern = compile(p); + this.pattern = p; + } + + /** + * Gets a copy of the date and time format symbols of this date format. + * + * @return the date and time format symbols of this date format + * @see #setDateFormatSymbols + */ + public DateFormatSymbols getDateFormatSymbols() + { + return (DateFormatSymbols)formatData.clone(); + } + + /** + * Sets the date and time format symbols of this date format. + * + * @param newFormatSymbols the new date and time format symbols + * @exception NullPointerException if the given newFormatSymbols is null + * @see #getDateFormatSymbols + */ + public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) + { + this.formatData = (DateFormatSymbols)newFormatSymbols.clone(); + useDateFormatSymbols = true; + } + + /** + * Creates a copy of this SimpleDateFormat. This also + * clones the format's date format symbols. + * + * @return a clone of this SimpleDateFormat + */ + public Object clone() { + SimpleDateFormat other = (SimpleDateFormat) super.clone(); + other.formatData = (DateFormatSymbols) formatData.clone(); + return other; + } + + /** + * Returns the hash code value for this SimpleDateFormat object. + * + * @return the hash code value for this SimpleDateFormat object. + */ + public int hashCode() + { + return pattern.hashCode(); + // just enough fields for a reasonable distribution + } + + /** + * Compares the given object with this SimpleDateFormat for + * equality. + * + * @return true if the given object is equal to this + * SimpleDateFormat + */ + public boolean equals(Object obj) + { + if (!super.equals(obj)) return false; // super does class check + SimpleDateFormat that = (SimpleDateFormat) obj; + return (pattern.equals(that.pattern) + && formatData.equals(that.formatData)); + } + + /** + * After reading an object from the input stream, the format + * pattern in the object is verified. + *

+ * @exception InvalidObjectException if the pattern is invalid + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException { + stream.defaultReadObject(); + + try { + compiledPattern = compile(pattern); + } catch (Exception e) { + throw new InvalidObjectException("invalid pattern"); + } + + if (serialVersionOnStream < 1) { + // didn't have defaultCenturyStart field + initializeDefaultCentury(); + } + else { + // fill in dependent transient field + parseAmbiguousDatesAsAfter(defaultCenturyStart); + } + serialVersionOnStream = currentSerialVersion; + + // If the deserialized object has a SimpleTimeZone, try + // to replace it with a ZoneInfo equivalent in order to + // be compatible with the SimpleTimeZone-based + // implementation as much as possible. + TimeZone tz = getTimeZone(); + if (tz instanceof SimpleTimeZone) { + String id = tz.getID(); + TimeZone zi = TimeZone.getTimeZone(id); + if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) { + setTimeZone(zi); + } + } + } + + /** + * Analyze the negative subpattern of DecimalFormat and set/update values + * as necessary. + */ + private void checkNegativeNumberExpression() { + if ((numberFormat instanceof DecimalFormat) && + !numberFormat.equals(originalNumberFormat)) { + String numberPattern = ((DecimalFormat)numberFormat).toPattern(); + if (!numberPattern.equals(originalNumberPattern)) { + hasFollowingMinusSign = false; + + int separatorIndex = numberPattern.indexOf(';'); + // If the negative subpattern is not absent, we have to analayze + // it in order to check if it has a following minus sign. + if (separatorIndex > -1) { + int minusIndex = numberPattern.indexOf('-', separatorIndex); + if ((minusIndex > numberPattern.lastIndexOf('0')) && + (minusIndex > numberPattern.lastIndexOf('#'))) { + hasFollowingMinusSign = true; + minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign(); + } + } + originalNumberPattern = numberPattern; + } + originalNumberFormat = numberFormat; + } + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/Calendar.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Calendar.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,2824 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996-1998 - All Rights Reserved + * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OptionalDataException; +import java.io.Serializable; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PermissionCollection; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import sun.util.BuddhistCalendar; +import sun.util.calendar.ZoneInfo; +import sun.util.resources.LocaleData; + +/** + * The Calendar class is an abstract class that provides methods + * for converting between a specific instant in time and a set of {@link + * #fields calendar fields} such as YEAR, MONTH, + * DAY_OF_MONTH, HOUR, and so on, and for + * manipulating the calendar fields, such as getting the date of the next + * week. An instant in time can be represented by a millisecond value that is + * an offset from the Epoch, January 1, 1970 + * 00:00:00.000 GMT (Gregorian). + * + *

The class also provides additional fields and methods for + * implementing a concrete calendar system outside the package. Those + * fields and methods are defined as protected. + * + *

+ * Like other locale-sensitive classes, Calendar provides a + * class method, getInstance, for getting a generally useful + * object of this type. Calendar's getInstance method + * returns a Calendar object whose + * calendar fields have been initialized with the current date and time: + *

+ *
+ *     Calendar rightNow = Calendar.getInstance();
+ * 
+ *
+ * + *

A Calendar object can produce all the calendar field values + * needed to implement the date-time formatting for a particular language and + * calendar style (for example, Japanese-Gregorian, Japanese-Traditional). + * Calendar defines the range of values returned by + * certain calendar fields, as well as their meaning. For example, + * the first month of the calendar system has value MONTH == + * JANUARY for all calendars. Other values are defined by the + * concrete subclass, such as ERA. See individual field + * documentation and subclass documentation for details. + * + *

Getting and Setting Calendar Field Values

+ * + *

The calendar field values can be set by calling the set + * methods. Any field values set in a Calendar will not be + * interpreted until it needs to calculate its time value (milliseconds from + * the Epoch) or values of the calendar fields. Calling the + * get, getTimeInMillis, getTime, + * add and roll involves such calculation. + * + *

Leniency

+ * + *

Calendar has two modes for interpreting the calendar + * fields, lenient and non-lenient. When a + * Calendar is in lenient mode, it accepts a wider range of + * calendar field values than it produces. When a Calendar + * recomputes calendar field values for return by get(), all of + * the calendar fields are normalized. For example, a lenient + * GregorianCalendar interprets MONTH == JANUARY, + * DAY_OF_MONTH == 32 as February 1. + + *

When a Calendar is in non-lenient mode, it throws an + * exception if there is any inconsistency in its calendar fields. For + * example, a GregorianCalendar always produces + * DAY_OF_MONTH values between 1 and the length of the month. A + * non-lenient GregorianCalendar throws an exception upon + * calculating its time or calendar field values if any out-of-range field + * value has been set. + * + *

First Week

+ * + * Calendar defines a locale-specific seven day week using two + * parameters: the first day of the week and the minimal days in first week + * (from 1 to 7). These numbers are taken from the locale resource data when a + * Calendar is constructed. They may also be specified explicitly + * through the methods for setting their values. + * + *

When setting or getting the WEEK_OF_MONTH or + * WEEK_OF_YEAR fields, Calendar must determine the + * first week of the month or year as a reference point. The first week of a + * month or year is defined as the earliest seven day period beginning on + * getFirstDayOfWeek() and containing at least + * getMinimalDaysInFirstWeek() days of that month or year. Weeks + * numbered ..., -1, 0 precede the first week; weeks numbered 2, 3,... follow + * it. Note that the normalized numbering returned by get() may be + * different. For example, a specific Calendar subclass may + * designate the week before week 1 of a year as week n of + * the previous year. + * + *

Calendar Fields Resolution

+ * + * When computing a date and time from the calendar fields, there + * may be insufficient information for the computation (such as only + * year and month with no day of month), or there may be inconsistent + * information (such as Tuesday, July 15, 1996 (Gregorian) -- July 15, + * 1996 is actually a Monday). Calendar will resolve + * calendar field values to determine the date and time in the + * following way. + * + *

If there is any conflict in calendar field values, + * Calendar gives priorities to calendar fields that have been set + * more recently. The following are the default combinations of the + * calendar fields. The most recent combination, as determined by the + * most recently set single field, will be used. + * + *

For the date fields: + *

+ *
+ * YEAR + MONTH + DAY_OF_MONTH
+ * YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
+ * YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
+ * YEAR + DAY_OF_YEAR
+ * YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
+ * 
+ * + * For the time of day fields: + *
+ *
+ * HOUR_OF_DAY
+ * AM_PM + HOUR
+ * 
+ * + *

If there are any calendar fields whose values haven't been set in the selected + * field combination, Calendar uses their default values. The default + * value of each field may vary by concrete calendar systems. For example, in + * GregorianCalendar, the default of a field is the same as that + * of the start of the Epoch: i.e., YEAR = 1970, MONTH = + * JANUARY, DAY_OF_MONTH = 1, etc. + * + *

+ * Note: There are certain possible ambiguities in + * interpretation of certain singular times, which are resolved in the + * following ways: + *

    + *
  1. 23:59 is the last minute of the day and 00:00 is the first + * minute of the next day. Thus, 23:59 on Dec 31, 1999 < 00:00 on + * Jan 1, 2000 < 00:01 on Jan 1, 2000. + * + *
  2. Although historically not precise, midnight also belongs to "am", + * and noon belongs to "pm", so on the same day, + * 12:00 am (midnight) < 12:01 am, and 12:00 pm (noon) < 12:01 pm + *
+ * + *

+ * The date or time format strings are not part of the definition of a + * calendar, as those must be modifiable or overridable by the user at + * runtime. Use {@link DateFormat} + * to format dates. + * + *

Field Manipulation

+ * + * The calendar fields can be changed using three methods: + * set(), add(), and roll().

+ * + *

set(f, value) changes calendar field + * f to value. In addition, it sets an + * internal member variable to indicate that calendar field f has + * been changed. Although calendar field f is changed immediately, + * the calendar's time value in milliseconds is not recomputed until the next call to + * get(), getTime(), getTimeInMillis(), + * add(), or roll() is made. Thus, multiple calls to + * set() do not trigger multiple, unnecessary + * computations. As a result of changing a calendar field using + * set(), other calendar fields may also change, depending on the + * calendar field, the calendar field value, and the calendar system. In addition, + * get(f) will not necessarily return value set by + * the call to the set method + * after the calendar fields have been recomputed. The specifics are determined by + * the concrete calendar class.

+ * + *

Example: Consider a GregorianCalendar + * originally set to August 31, 1999. Calling set(Calendar.MONTH, + * Calendar.SEPTEMBER) sets the date to September 31, + * 1999. This is a temporary internal representation that resolves to + * October 1, 1999 if getTime()is then called. However, a + * call to set(Calendar.DAY_OF_MONTH, 30) before the call to + * getTime() sets the date to September 30, 1999, since + * no recomputation occurs after set() itself.

+ * + *

add(f, delta) adds delta + * to field f. This is equivalent to calling set(f, + * get(f) + delta) with two adjustments:

+ * + *
+ *

Add rule 1. The value of field f + * after the call minus the value of field f before the + * call is delta, modulo any overflow that has occurred in + * field f. Overflow occurs when a field value exceeds its + * range and, as a result, the next larger field is incremented or + * decremented and the field value is adjusted back into its range.

+ * + *

Add rule 2. If a smaller field is expected to be + * invariant, but it is impossible for it to be equal to its + * prior value because of changes in its minimum or maximum after field + * f is changed or other constraints, such as time zone + * offset changes, then its value is adjusted to be as close + * as possible to its expected value. A smaller field represents a + * smaller unit of time. HOUR is a smaller field than + * DAY_OF_MONTH. No adjustment is made to smaller fields + * that are not expected to be invariant. The calendar system + * determines what fields are expected to be invariant.

+ *
+ * + *

In addition, unlike set(), add() forces + * an immediate recomputation of the calendar's milliseconds and all + * fields.

+ * + *

Example: Consider a GregorianCalendar + * originally set to August 31, 1999. Calling add(Calendar.MONTH, + * 13) sets the calendar to September 30, 2000. Add rule + * 1 sets the MONTH field to September, since + * adding 13 months to August gives September of the next year. Since + * DAY_OF_MONTH cannot be 31 in September in a + * GregorianCalendar, add rule 2 sets the + * DAY_OF_MONTH to 30, the closest possible value. Although + * it is a smaller field, DAY_OF_WEEK is not adjusted by + * rule 2, since it is expected to change when the month changes in a + * GregorianCalendar.

+ * + *

roll(f, delta) adds + * delta to field f without changing larger + * fields. This is equivalent to calling add(f, delta) with + * the following adjustment:

+ * + *
+ *

Roll rule. Larger fields are unchanged after the + * call. A larger field represents a larger unit of + * time. DAY_OF_MONTH is a larger field than + * HOUR.

+ *
+ * + *

Example: See {@link java.util.GregorianCalendar#roll(int, int)}. + * + *

Usage model. To motivate the behavior of + * add() and roll(), consider a user interface + * component with increment and decrement buttons for the month, day, and + * year, and an underlying GregorianCalendar. If the + * interface reads January 31, 1999 and the user presses the month + * increment button, what should it read? If the underlying + * implementation uses set(), it might read March 3, 1999. A + * better result would be February 28, 1999. Furthermore, if the user + * presses the month increment button again, it should read March 31, + * 1999, not March 28, 1999. By saving the original date and using either + * add() or roll(), depending on whether larger + * fields should be affected, the user interface can behave as most users + * will intuitively expect.

+ * + * @see java.lang.System#currentTimeMillis() + * @see Date + * @see GregorianCalendar + * @see TimeZone + * @see java.text.DateFormat + * @author Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu + * @since JDK1.1 + */ +public abstract class Calendar implements Serializable, Cloneable, Comparable { + + // Data flow in Calendar + // --------------------- + + // The current time is represented in two ways by Calendar: as UTC + // milliseconds from the epoch (1 January 1970 0:00 UTC), and as local + // fields such as MONTH, HOUR, AM_PM, etc. It is possible to compute the + // millis from the fields, and vice versa. The data needed to do this + // conversion is encapsulated by a TimeZone object owned by the Calendar. + // The data provided by the TimeZone object may also be overridden if the + // user sets the ZONE_OFFSET and/or DST_OFFSET fields directly. The class + // keeps track of what information was most recently set by the caller, and + // uses that to compute any other information as needed. + + // If the user sets the fields using set(), the data flow is as follows. + // This is implemented by the Calendar subclass's computeTime() method. + // During this process, certain fields may be ignored. The disambiguation + // algorithm for resolving which fields to pay attention to is described + // in the class documentation. + + // local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.) + // | + // | Using Calendar-specific algorithm + // V + // local standard millis + // | + // | Using TimeZone or user-set ZONE_OFFSET / DST_OFFSET + // V + // UTC millis (in time data member) + + // If the user sets the UTC millis using setTime() or setTimeInMillis(), + // the data flow is as follows. This is implemented by the Calendar + // subclass's computeFields() method. + + // UTC millis (in time data member) + // | + // | Using TimeZone getOffset() + // V + // local standard millis + // | + // | Using Calendar-specific algorithm + // V + // local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.) + + // In general, a round trip from fields, through local and UTC millis, and + // back out to fields is made when necessary. This is implemented by the + // complete() method. Resolving a partial set of fields into a UTC millis + // value allows all remaining fields to be generated from that value. If + // the Calendar is lenient, the fields are also renormalized to standard + // ranges when they are regenerated. + + /** + * Field number for get and set indicating the + * era, e.g., AD or BC in the Julian calendar. This is a calendar-specific + * value; see subclass documentation. + * + * @see GregorianCalendar#AD + * @see GregorianCalendar#BC + */ + public final static int ERA = 0; + + /** + * Field number for get and set indicating the + * year. This is a calendar-specific value; see subclass documentation. + */ + public final static int YEAR = 1; + + /** + * Field number for get and set indicating the + * month. This is a calendar-specific value. The first month of + * the year in the Gregorian and Julian calendars is + * JANUARY which is 0; the last depends on the number + * of months in a year. + * + * @see #JANUARY + * @see #FEBRUARY + * @see #MARCH + * @see #APRIL + * @see #MAY + * @see #JUNE + * @see #JULY + * @see #AUGUST + * @see #SEPTEMBER + * @see #OCTOBER + * @see #NOVEMBER + * @see #DECEMBER + * @see #UNDECIMBER + */ + public final static int MONTH = 2; + + /** + * Field number for get and set indicating the + * week number within the current year. The first week of the year, as + * defined by getFirstDayOfWeek() and + * getMinimalDaysInFirstWeek(), has value 1. Subclasses define + * the value of WEEK_OF_YEAR for days before the first week of + * the year. + * + * @see #getFirstDayOfWeek + * @see #getMinimalDaysInFirstWeek + */ + public final static int WEEK_OF_YEAR = 3; + + /** + * Field number for get and set indicating the + * week number within the current month. The first week of the month, as + * defined by getFirstDayOfWeek() and + * getMinimalDaysInFirstWeek(), has value 1. Subclasses define + * the value of WEEK_OF_MONTH for days before the first week of + * the month. + * + * @see #getFirstDayOfWeek + * @see #getMinimalDaysInFirstWeek + */ + public final static int WEEK_OF_MONTH = 4; + + /** + * Field number for get and set indicating the + * day of the month. This is a synonym for DAY_OF_MONTH. + * The first day of the month has value 1. + * + * @see #DAY_OF_MONTH + */ + public final static int DATE = 5; + + /** + * Field number for get and set indicating the + * day of the month. This is a synonym for DATE. + * The first day of the month has value 1. + * + * @see #DATE + */ + public final static int DAY_OF_MONTH = 5; + + /** + * Field number for get and set indicating the day + * number within the current year. The first day of the year has value 1. + */ + public final static int DAY_OF_YEAR = 6; + + /** + * Field number for get and set indicating the day + * of the week. This field takes values SUNDAY, + * MONDAY, TUESDAY, WEDNESDAY, + * THURSDAY, FRIDAY, and SATURDAY. + * + * @see #SUNDAY + * @see #MONDAY + * @see #TUESDAY + * @see #WEDNESDAY + * @see #THURSDAY + * @see #FRIDAY + * @see #SATURDAY + */ + public final static int DAY_OF_WEEK = 7; + + /** + * Field number for get and set indicating the + * ordinal number of the day of the week within the current month. Together + * with the DAY_OF_WEEK field, this uniquely specifies a day + * within a month. Unlike WEEK_OF_MONTH and + * WEEK_OF_YEAR, this field's value does not depend on + * getFirstDayOfWeek() or + * getMinimalDaysInFirstWeek(). DAY_OF_MONTH 1 + * through 7 always correspond to DAY_OF_WEEK_IN_MONTH + * 1; 8 through 14 correspond to + * DAY_OF_WEEK_IN_MONTH 2, and so on. + * DAY_OF_WEEK_IN_MONTH 0 indicates the week before + * DAY_OF_WEEK_IN_MONTH 1. Negative values count back from the + * end of the month, so the last Sunday of a month is specified as + * DAY_OF_WEEK = SUNDAY, DAY_OF_WEEK_IN_MONTH = -1. Because + * negative values count backward they will usually be aligned differently + * within the month than positive values. For example, if a month has 31 + * days, DAY_OF_WEEK_IN_MONTH -1 will overlap + * DAY_OF_WEEK_IN_MONTH 5 and the end of 4. + * + * @see #DAY_OF_WEEK + * @see #WEEK_OF_MONTH + */ + public final static int DAY_OF_WEEK_IN_MONTH = 8; + + /** + * Field number for get and set indicating + * whether the HOUR is before or after noon. + * E.g., at 10:04:15.250 PM the AM_PM is PM. + * + * @see #AM + * @see #PM + * @see #HOUR + */ + public final static int AM_PM = 9; + + /** + * Field number for get and set indicating the + * hour of the morning or afternoon. HOUR is used for the + * 12-hour clock (0 - 11). Noon and midnight are represented by 0, not by 12. + * E.g., at 10:04:15.250 PM the HOUR is 10. + * + * @see #AM_PM + * @see #HOUR_OF_DAY + */ + public final static int HOUR = 10; + + /** + * Field number for get and set indicating the + * hour of the day. HOUR_OF_DAY is used for the 24-hour clock. + * E.g., at 10:04:15.250 PM the HOUR_OF_DAY is 22. + * + * @see #HOUR + */ + public final static int HOUR_OF_DAY = 11; + + /** + * Field number for get and set indicating the + * minute within the hour. + * E.g., at 10:04:15.250 PM the MINUTE is 4. + */ + public final static int MINUTE = 12; + + /** + * Field number for get and set indicating the + * second within the minute. + * E.g., at 10:04:15.250 PM the SECOND is 15. + */ + public final static int SECOND = 13; + + /** + * Field number for get and set indicating the + * millisecond within the second. + * E.g., at 10:04:15.250 PM the MILLISECOND is 250. + */ + public final static int MILLISECOND = 14; + + /** + * Field number for get and set + * indicating the raw offset from GMT in milliseconds. + *

+ * This field reflects the correct GMT offset value of the time + * zone of this Calendar if the + * TimeZone implementation subclass supports + * historical GMT offset changes. + */ + public final static int ZONE_OFFSET = 15; + + /** + * Field number for get and set indicating the + * daylight saving offset in milliseconds. + *

+ * This field reflects the correct daylight saving offset value of + * the time zone of this Calendar if the + * TimeZone implementation subclass supports + * historical Daylight Saving Time schedule changes. + */ + public final static int DST_OFFSET = 16; + + /** + * The number of distinct fields recognized by get and set. + * Field numbers range from 0..FIELD_COUNT-1. + */ + public final static int FIELD_COUNT = 17; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Sunday. + */ + public final static int SUNDAY = 1; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Monday. + */ + public final static int MONDAY = 2; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Tuesday. + */ + public final static int TUESDAY = 3; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Wednesday. + */ + public final static int WEDNESDAY = 4; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Thursday. + */ + public final static int THURSDAY = 5; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Friday. + */ + public final static int FRIDAY = 6; + + /** + * Value of the {@link #DAY_OF_WEEK} field indicating + * Saturday. + */ + public final static int SATURDAY = 7; + + /** + * Value of the {@link #MONTH} field indicating the + * first month of the year in the Gregorian and Julian calendars. + */ + public final static int JANUARY = 0; + + /** + * Value of the {@link #MONTH} field indicating the + * second month of the year in the Gregorian and Julian calendars. + */ + public final static int FEBRUARY = 1; + + /** + * Value of the {@link #MONTH} field indicating the + * third month of the year in the Gregorian and Julian calendars. + */ + public final static int MARCH = 2; + + /** + * Value of the {@link #MONTH} field indicating the + * fourth month of the year in the Gregorian and Julian calendars. + */ + public final static int APRIL = 3; + + /** + * Value of the {@link #MONTH} field indicating the + * fifth month of the year in the Gregorian and Julian calendars. + */ + public final static int MAY = 4; + + /** + * Value of the {@link #MONTH} field indicating the + * sixth month of the year in the Gregorian and Julian calendars. + */ + public final static int JUNE = 5; + + /** + * Value of the {@link #MONTH} field indicating the + * seventh month of the year in the Gregorian and Julian calendars. + */ + public final static int JULY = 6; + + /** + * Value of the {@link #MONTH} field indicating the + * eighth month of the year in the Gregorian and Julian calendars. + */ + public final static int AUGUST = 7; + + /** + * Value of the {@link #MONTH} field indicating the + * ninth month of the year in the Gregorian and Julian calendars. + */ + public final static int SEPTEMBER = 8; + + /** + * Value of the {@link #MONTH} field indicating the + * tenth month of the year in the Gregorian and Julian calendars. + */ + public final static int OCTOBER = 9; + + /** + * Value of the {@link #MONTH} field indicating the + * eleventh month of the year in the Gregorian and Julian calendars. + */ + public final static int NOVEMBER = 10; + + /** + * Value of the {@link #MONTH} field indicating the + * twelfth month of the year in the Gregorian and Julian calendars. + */ + public final static int DECEMBER = 11; + + /** + * Value of the {@link #MONTH} field indicating the + * thirteenth month of the year. Although GregorianCalendar + * does not use this value, lunar calendars do. + */ + public final static int UNDECIMBER = 12; + + /** + * Value of the {@link #AM_PM} field indicating the + * period of the day from midnight to just before noon. + */ + public final static int AM = 0; + + /** + * Value of the {@link #AM_PM} field indicating the + * period of the day from noon to just before midnight. + */ + public final static int PM = 1; + + /** + * A style specifier for {@link #getDisplayNames(int, int, Locale) + * getDisplayNames} indicating names in all styles, such as + * "January" and "Jan". + * + * @see #SHORT + * @see #LONG + * @since 1.6 + */ + public static final int ALL_STYLES = 0; + + /** + * A style specifier for {@link #getDisplayName(int, int, Locale) + * getDisplayName} and {@link #getDisplayNames(int, int, Locale) + * getDisplayNames} indicating a short name, such as "Jan". + * + * @see #LONG + * @since 1.6 + */ + public static final int SHORT = 1; + + /** + * A style specifier for {@link #getDisplayName(int, int, Locale) + * getDisplayName} and {@link #getDisplayNames(int, int, Locale) + * getDisplayNames} indicating a long name, such as "January". + * + * @see #SHORT + * @since 1.6 + */ + public static final int LONG = 2; + + // Internal notes: + // Calendar contains two kinds of time representations: current "time" in + // milliseconds, and a set of calendar "fields" representing the current time. + // The two representations are usually in sync, but can get out of sync + // as follows. + // 1. Initially, no fields are set, and the time is invalid. + // 2. If the time is set, all fields are computed and in sync. + // 3. If a single field is set, the time is invalid. + // Recomputation of the time and fields happens when the object needs + // to return a result to the user, or use a result for a computation. + + /** + * The calendar field values for the currently set time for this calendar. + * This is an array of FIELD_COUNT integers, with index values + * ERA through DST_OFFSET. + * @serial + */ + protected int fields[]; + + /** + * The flags which tell if a specified calendar field for the calendar is set. + * A new object has no fields set. After the first call to a method + * which generates the fields, they all remain set after that. + * This is an array of FIELD_COUNT booleans, with index values + * ERA through DST_OFFSET. + * @serial + */ + protected boolean isSet[]; + + /** + * Pseudo-time-stamps which specify when each field was set. There + * are two special values, UNSET and COMPUTED. Values from + * MINIMUM_USER_SET to Integer.MAX_VALUE are legal user set values. + */ + transient private int stamp[]; + + /** + * The currently set time for this calendar, expressed in milliseconds after + * January 1, 1970, 0:00:00 GMT. + * @see #isTimeSet + * @serial + */ + protected long time; + + /** + * True if then the value of time is valid. + * The time is made invalid by a change to an item of field[]. + * @see #time + * @serial + */ + protected boolean isTimeSet; + + /** + * True if fields[] are in sync with the currently set time. + * If false, then the next attempt to get the value of a field will + * force a recomputation of all fields from the current value of + * time. + * @serial + */ + protected boolean areFieldsSet; + + /** + * True if all fields have been set. + * @serial + */ + transient boolean areAllFieldsSet; + + /** + * True if this calendar allows out-of-range field values during computation + * of time from fields[]. + * @see #setLenient + * @see #isLenient + * @serial + */ + private boolean lenient = true; + + /** + * The TimeZone used by this calendar. Calendar + * uses the time zone data to translate between locale and GMT time. + * @serial + */ + private TimeZone zone; + + /** + * True if zone references to a shared TimeZone object. + */ + transient private boolean sharedZone = false; + + /** + * The first day of the week, with possible values SUNDAY, + * MONDAY, etc. This is a locale-dependent value. + * @serial + */ + private int firstDayOfWeek; + + /** + * The number of days required for the first week in a month or year, + * with possible values from 1 to 7. This is a locale-dependent value. + * @serial + */ + private int minimalDaysInFirstWeek; + + /** + * Cache to hold the firstDayOfWeek and minimalDaysInFirstWeek + * of a Locale. + */ + private static final ConcurrentMap cachedLocaleData + = new ConcurrentHashMap(3); + + // Special values of stamp[] + /** + * The corresponding fields[] has no value. + */ + private static final int UNSET = 0; + + /** + * The value of the corresponding fields[] has been calculated internally. + */ + private static final int COMPUTED = 1; + + /** + * The value of the corresponding fields[] has been set externally. Stamp + * values which are greater than 1 represents the (pseudo) time when the + * corresponding fields[] value was set. + */ + private static final int MINIMUM_USER_STAMP = 2; + + /** + * The mask value that represents all of the fields. + */ + static final int ALL_FIELDS = (1 << FIELD_COUNT) - 1; + + /** + * The next available value for stamp[], an internal array. + * This actually should not be written out to the stream, and will probably + * be removed from the stream in the near future. In the meantime, + * a value of MINIMUM_USER_STAMP should be used. + * @serial + */ + private int nextStamp = MINIMUM_USER_STAMP; + + // the internal serial version which says which version was written + // - 0 (default) for version up to JDK 1.1.5 + // - 1 for version from JDK 1.1.6, which writes a correct 'time' value + // as well as compatible values for other fields. This is a + // transitional format. + // - 2 (not implemented yet) a future version, in which fields[], + // areFieldsSet, and isTimeSet become transient, and isSet[] is + // removed. In JDK 1.1.6 we write a format compatible with version 2. + static final int currentSerialVersion = 1; + + /** + * The version of the serialized data on the stream. Possible values: + *

+ *
0 or not present on stream
+ *
+ * JDK 1.1.5 or earlier. + *
+ *
1
+ *
+ * JDK 1.1.6 or later. Writes a correct 'time' value + * as well as compatible values for other fields. This is a + * transitional format. + *
+ *
+ * When streaming out this class, the most recent format + * and the highest allowable serialVersionOnStream + * is written. + * @serial + * @since JDK1.1.6 + */ + private int serialVersionOnStream = currentSerialVersion; + + // Proclaim serialization compatibility with JDK 1.1 + static final long serialVersionUID = -1807547505821590642L; + + // Mask values for calendar fields + final static int ERA_MASK = (1 << ERA); + final static int YEAR_MASK = (1 << YEAR); + final static int MONTH_MASK = (1 << MONTH); + final static int WEEK_OF_YEAR_MASK = (1 << WEEK_OF_YEAR); + final static int WEEK_OF_MONTH_MASK = (1 << WEEK_OF_MONTH); + final static int DAY_OF_MONTH_MASK = (1 << DAY_OF_MONTH); + final static int DATE_MASK = DAY_OF_MONTH_MASK; + final static int DAY_OF_YEAR_MASK = (1 << DAY_OF_YEAR); + final static int DAY_OF_WEEK_MASK = (1 << DAY_OF_WEEK); + final static int DAY_OF_WEEK_IN_MONTH_MASK = (1 << DAY_OF_WEEK_IN_MONTH); + final static int AM_PM_MASK = (1 << AM_PM); + final static int HOUR_MASK = (1 << HOUR); + final static int HOUR_OF_DAY_MASK = (1 << HOUR_OF_DAY); + final static int MINUTE_MASK = (1 << MINUTE); + final static int SECOND_MASK = (1 << SECOND); + final static int MILLISECOND_MASK = (1 << MILLISECOND); + final static int ZONE_OFFSET_MASK = (1 << ZONE_OFFSET); + final static int DST_OFFSET_MASK = (1 << DST_OFFSET); + + /** + * Constructs a Calendar with the default time zone + * and locale. + * @see TimeZone#getDefault + */ + protected Calendar() + { + this(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT)); + sharedZone = true; + } + + /** + * Constructs a calendar with the specified time zone and locale. + * + * @param zone the time zone to use + * @param aLocale the locale for the week data + */ + protected Calendar(TimeZone zone, Locale aLocale) + { + fields = new int[FIELD_COUNT]; + isSet = new boolean[FIELD_COUNT]; + stamp = new int[FIELD_COUNT]; + + this.zone = zone; + setWeekCountData(aLocale); + } + + /** + * Gets a calendar using the default time zone and locale. The + * Calendar returned is based on the current time + * in the default time zone with the default locale. + * + * @return a Calendar. + */ + public static Calendar getInstance() + { + Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT)); + cal.sharedZone = true; + return cal; + } + + /** + * Gets a calendar using the specified time zone and default locale. + * The Calendar returned is based on the current time + * in the given time zone with the default locale. + * + * @param zone the time zone to use + * @return a Calendar. + */ + public static Calendar getInstance(TimeZone zone) + { + return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT)); + } + + /** + * Gets a calendar using the default time zone and specified locale. + * The Calendar returned is based on the current time + * in the default time zone with the given locale. + * + * @param aLocale the locale for the week data + * @return a Calendar. + */ + public static Calendar getInstance(Locale aLocale) + { + Calendar cal = createCalendar(TimeZone.getDefaultRef(), aLocale); + cal.sharedZone = true; + return cal; + } + + /** + * Gets a calendar with the specified time zone and locale. + * The Calendar returned is based on the current time + * in the given time zone with the given locale. + * + * @param zone the time zone to use + * @param aLocale the locale for the week data + * @return a Calendar. + */ + public static Calendar getInstance(TimeZone zone, + Locale aLocale) + { + return createCalendar(zone, aLocale); + } + + private static Calendar createCalendar(TimeZone zone, + Locale aLocale) + { + Calendar cal = null; + + String caltype = aLocale.getUnicodeLocaleType("ca"); + if (caltype == null) { + // Calendar type is not specified. + // If the specified locale is a Thai locale, + // returns a BuddhistCalendar instance. + if ("th".equals(aLocale.getLanguage()) + && ("TH".equals(aLocale.getCountry()))) { + cal = new BuddhistCalendar(zone, aLocale); + } else { + cal = new GregorianCalendar(zone, aLocale); + } + } else if (caltype.equals("japanese")) { + cal = new JapaneseImperialCalendar(zone, aLocale); + } else if (caltype.equals("buddhist")) { + cal = new BuddhistCalendar(zone, aLocale); + } else { + // Unsupported calendar type. + // Use Gregorian calendar as a fallback. + cal = new GregorianCalendar(zone, aLocale); + } + + return cal; + } + + /** + * Returns an array of all locales for which the getInstance + * methods of this class can return localized instances. + * The array returned must contain at least a Locale + * instance equal to {@link java.util.Locale#US Locale.US}. + * + * @return An array of locales for which localized + * Calendar instances are available. + */ + public static synchronized Locale[] getAvailableLocales() + { + return DateFormat.getAvailableLocales(); + } + + /** + * Converts the current calendar field values in {@link #fields fields[]} + * to the millisecond time value + * {@link #time}. + * + * @see #complete() + * @see #computeFields() + */ + protected abstract void computeTime(); + + /** + * Converts the current millisecond time value {@link #time} + * to calendar field values in {@link #fields fields[]}. + * This allows you to sync up the calendar field values with + * a new time that is set for the calendar. The time is not + * recomputed first; to recompute the time, then the fields, call the + * {@link #complete()} method. + * + * @see #computeTime() + */ + protected abstract void computeFields(); + + /** + * Returns a Date object representing this + * Calendar's time value (millisecond offset from the Epoch"). + * + * @return a Date representing the time value. + * @see #setTime(Date) + * @see #getTimeInMillis() + */ + public final Date getTime() { + return new Date(getTimeInMillis()); + } + + /** + * Sets this Calendar's time with the given Date. + *

+ * Note: Calling setTime() with + * Date(Long.MAX_VALUE) or Date(Long.MIN_VALUE) + * may yield incorrect field values from get(). + * + * @param date the given Date. + * @see #getTime() + * @see #setTimeInMillis(long) + */ + public final void setTime(Date date) { + setTimeInMillis(date.getTime()); + } + + /** + * Returns this Calendar's time value in milliseconds. + * + * @return the current time as UTC milliseconds from the epoch. + * @see #getTime() + * @see #setTimeInMillis(long) + */ + public long getTimeInMillis() { + if (!isTimeSet) { + updateTime(); + } + return time; + } + + /** + * Sets this Calendar's current time from the given long value. + * + * @param millis the new time in UTC milliseconds from the epoch. + * @see #setTime(Date) + * @see #getTimeInMillis() + */ + public void setTimeInMillis(long millis) { + // If we don't need to recalculate the calendar field values, + // do nothing. + if (time == millis && isTimeSet && areFieldsSet && areAllFieldsSet + && (zone instanceof ZoneInfo) && !((ZoneInfo)zone).isDirty()) { + return; + } + time = millis; + isTimeSet = true; + areFieldsSet = false; + computeFields(); + areAllFieldsSet = areFieldsSet = true; + } + + /** + * Returns the value of the given calendar field. In lenient mode, + * all calendar fields are normalized. In non-lenient mode, all + * calendar fields are validated and this method throws an + * exception if any calendar fields have out-of-range values. The + * normalization and validation are handled by the + * {@link #complete()} method, which process is calendar + * system dependent. + * + * @param field the given calendar field. + * @return the value for the given calendar field. + * @throws ArrayIndexOutOfBoundsException if the specified field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #set(int,int) + * @see #complete() + */ + public int get(int field) + { + complete(); + return internalGet(field); + } + + /** + * Returns the value of the given calendar field. This method does + * not involve normalization or validation of the field value. + * + * @param field the given calendar field. + * @return the value for the given calendar field. + * @see #get(int) + */ + protected final int internalGet(int field) + { + return fields[field]; + } + + /** + * Sets the value of the given calendar field. This method does + * not affect any setting state of the field in this + * Calendar instance. + * + * @throws IndexOutOfBoundsException if the specified field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #areFieldsSet + * @see #isTimeSet + * @see #areAllFieldsSet + * @see #set(int,int) + */ + final void internalSet(int field, int value) + { + fields[field] = value; + } + + /** + * Sets the given calendar field to the given value. The value is not + * interpreted by this method regardless of the leniency mode. + * + * @param field the given calendar field. + * @param value the value to be set for the given calendar field. + * @throws ArrayIndexOutOfBoundsException if the specified field is out of range + * (field < 0 || field >= FIELD_COUNT). + * in non-lenient mode. + * @see #set(int,int,int) + * @see #set(int,int,int,int,int) + * @see #set(int,int,int,int,int,int) + * @see #get(int) + */ + public void set(int field, int value) + { + // If the fields are partially normalized, calculate all the + // fields before changing any fields. + if (areFieldsSet && !areAllFieldsSet) { + computeFields(); + } + internalSet(field, value); + isTimeSet = false; + areFieldsSet = false; + isSet[field] = true; + stamp[field] = nextStamp++; + if (nextStamp == Integer.MAX_VALUE) { + adjustStamp(); + } + } + + /** + * Sets the values for the calendar fields YEAR, + * MONTH, and DAY_OF_MONTH. + * Previous values of other calendar fields are retained. If this is not desired, + * call {@link #clear()} first. + * + * @param year the value used to set the YEAR calendar field. + * @param month the value used to set the MONTH calendar field. + * Month value is 0-based. e.g., 0 for January. + * @param date the value used to set the DAY_OF_MONTH calendar field. + * @see #set(int,int) + * @see #set(int,int,int,int,int) + * @see #set(int,int,int,int,int,int) + */ + public final void set(int year, int month, int date) + { + set(YEAR, year); + set(MONTH, month); + set(DATE, date); + } + + /** + * Sets the values for the calendar fields YEAR, + * MONTH, DAY_OF_MONTH, + * HOUR_OF_DAY, and MINUTE. + * Previous values of other fields are retained. If this is not desired, + * call {@link #clear()} first. + * + * @param year the value used to set the YEAR calendar field. + * @param month the value used to set the MONTH calendar field. + * Month value is 0-based. e.g., 0 for January. + * @param date the value used to set the DAY_OF_MONTH calendar field. + * @param hourOfDay the value used to set the HOUR_OF_DAY calendar field. + * @param minute the value used to set the MINUTE calendar field. + * @see #set(int,int) + * @see #set(int,int,int) + * @see #set(int,int,int,int,int,int) + */ + public final void set(int year, int month, int date, int hourOfDay, int minute) + { + set(YEAR, year); + set(MONTH, month); + set(DATE, date); + set(HOUR_OF_DAY, hourOfDay); + set(MINUTE, minute); + } + + /** + * Sets the values for the fields YEAR, MONTH, + * DAY_OF_MONTH, HOUR, MINUTE, and + * SECOND. + * Previous values of other fields are retained. If this is not desired, + * call {@link #clear()} first. + * + * @param year the value used to set the YEAR calendar field. + * @param month the value used to set the MONTH calendar field. + * Month value is 0-based. e.g., 0 for January. + * @param date the value used to set the DAY_OF_MONTH calendar field. + * @param hourOfDay the value used to set the HOUR_OF_DAY calendar field. + * @param minute the value used to set the MINUTE calendar field. + * @param second the value used to set the SECOND calendar field. + * @see #set(int,int) + * @see #set(int,int,int) + * @see #set(int,int,int,int,int) + */ + public final void set(int year, int month, int date, int hourOfDay, int minute, + int second) + { + set(YEAR, year); + set(MONTH, month); + set(DATE, date); + set(HOUR_OF_DAY, hourOfDay); + set(MINUTE, minute); + set(SECOND, second); + } + + /** + * Sets all the calendar field values and the time value + * (millisecond offset from the Epoch) of + * this Calendar undefined. This means that {@link + * #isSet(int) isSet()} will return false for all the + * calendar fields, and the date and time calculations will treat + * the fields as if they had never been set. A + * Calendar implementation class may use its specific + * default field values for date/time calculations. For example, + * GregorianCalendar uses 1970 if the + * YEAR field value is undefined. + * + * @see #clear(int) + */ + public final void clear() + { + for (int i = 0; i < fields.length; ) { + stamp[i] = fields[i] = 0; // UNSET == 0 + isSet[i++] = false; + } + areAllFieldsSet = areFieldsSet = false; + isTimeSet = false; + } + + /** + * Sets the given calendar field value and the time value + * (millisecond offset from the Epoch) of + * this Calendar undefined. This means that {@link + * #isSet(int) isSet(field)} will return false, and + * the date and time calculations will treat the field as if it + * had never been set. A Calendar implementation + * class may use the field's specific default value for date and + * time calculations. + * + *

The {@link #HOUR_OF_DAY}, {@link #HOUR} and {@link #AM_PM} + * fields are handled independently and the the resolution rule for the time of + * day is applied. Clearing one of the fields doesn't reset + * the hour of day value of this Calendar. Use {@link + * #set(int,int) set(Calendar.HOUR_OF_DAY, 0)} to reset the hour + * value. + * + * @param field the calendar field to be cleared. + * @see #clear() + */ + public final void clear(int field) + { + fields[field] = 0; + stamp[field] = UNSET; + isSet[field] = false; + + areAllFieldsSet = areFieldsSet = false; + isTimeSet = false; + } + + /** + * Determines if the given calendar field has a value set, + * including cases that the value has been set by internal fields + * calculations triggered by a get method call. + * + * @return true if the given calendar field has a value set; + * false otherwise. + */ + public final boolean isSet(int field) + { + return stamp[field] != UNSET; + } + + /** + * Returns the string representation of the calendar + * field value in the given style and + * locale. If no string representation is + * applicable, null is returned. This method calls + * {@link Calendar#get(int) get(field)} to get the calendar + * field value if the string representation is + * applicable to the given calendar field. + * + *

For example, if this Calendar is a + * GregorianCalendar and its date is 2005-01-01, then + * the string representation of the {@link #MONTH} field would be + * "January" in the long style in an English locale or "Jan" in + * the short style. However, no string representation would be + * available for the {@link #DAY_OF_MONTH} field, and this method + * would return null. + * + *

The default implementation supports the calendar fields for + * which a {@link DateFormatSymbols} has names in the given + * locale. + * + * @param field + * the calendar field for which the string representation + * is returned + * @param style + * the style applied to the string representation; one of + * {@link #SHORT} or {@link #LONG}. + * @param locale + * the locale for the string representation + * @return the string representation of the given + * field in the given style, or + * null if no string representation is + * applicable. + * @exception IllegalArgumentException + * if field or style is invalid, + * or if this Calendar is non-lenient and any + * of the calendar fields have invalid values + * @exception NullPointerException + * if locale is null + * @since 1.6 + */ + public String getDisplayName(int field, int style, Locale locale) { + if (!checkDisplayNameParams(field, style, ALL_STYLES, LONG, locale, + ERA_MASK|MONTH_MASK|DAY_OF_WEEK_MASK|AM_PM_MASK)) { + return null; + } + + DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale); + String[] strings = getFieldStrings(field, style, symbols); + if (strings != null) { + int fieldValue = get(field); + if (fieldValue < strings.length) { + return strings[fieldValue]; + } + } + return null; + } + + /** + * Returns a Map containing all names of the calendar + * field in the given style and + * locale and their corresponding field values. For + * example, if this Calendar is a {@link + * GregorianCalendar}, the returned map would contain "Jan" to + * {@link #JANUARY}, "Feb" to {@link #FEBRUARY}, and so on, in the + * {@linkplain #SHORT short} style in an English locale. + * + *

The values of other calendar fields may be taken into + * account to determine a set of display names. For example, if + * this Calendar is a lunisolar calendar system and + * the year value given by the {@link #YEAR} field has a leap + * month, this method would return month names containing the leap + * month name, and month names are mapped to their values specific + * for the year. + * + *

The default implementation supports display names contained in + * a {@link DateFormatSymbols}. For example, if field + * is {@link #MONTH} and style is {@link + * #ALL_STYLES}, this method returns a Map containing + * all strings returned by {@link DateFormatSymbols#getShortMonths()} + * and {@link DateFormatSymbols#getMonths()}. + * + * @param field + * the calendar field for which the display names are returned + * @param style + * the style applied to the display names; one of {@link + * #SHORT}, {@link #LONG}, or {@link #ALL_STYLES}. + * @param locale + * the locale for the display names + * @return a Map containing all display names in + * style and locale and their + * field values, or null if no display names + * are defined for field + * @exception IllegalArgumentException + * if field or style is invalid, + * or if this Calendar is non-lenient and any + * of the calendar fields have invalid values + * @exception NullPointerException + * if locale is null + * @since 1.6 + */ + public Map getDisplayNames(int field, int style, Locale locale) { + if (!checkDisplayNameParams(field, style, ALL_STYLES, LONG, locale, + ERA_MASK|MONTH_MASK|DAY_OF_WEEK_MASK|AM_PM_MASK)) { + return null; + } + + // ALL_STYLES + if (style == ALL_STYLES) { + Map shortNames = getDisplayNamesImpl(field, SHORT, locale); + if (field == ERA || field == AM_PM) { + return shortNames; + } + Map longNames = getDisplayNamesImpl(field, LONG, locale); + if (shortNames == null) { + return longNames; + } + if (longNames != null) { + shortNames.putAll(longNames); + } + return shortNames; + } + + // SHORT or LONG + return getDisplayNamesImpl(field, style, locale); + } + + private Map getDisplayNamesImpl(int field, int style, Locale locale) { + DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale); + String[] strings = getFieldStrings(field, style, symbols); + if (strings != null) { + Map names = new HashMap(); + for (int i = 0; i < strings.length; i++) { + if (strings[i].length() == 0) { + continue; + } + names.put(strings[i], i); + } + return names; + } + return null; + } + + boolean checkDisplayNameParams(int field, int style, int minStyle, int maxStyle, + Locale locale, int fieldMask) { + if (field < 0 || field >= fields.length || + style < minStyle || style > maxStyle) { + throw new IllegalArgumentException(); + } + if (locale == null) { + throw new NullPointerException(); + } + return isFieldSet(fieldMask, field); + } + + private String[] getFieldStrings(int field, int style, DateFormatSymbols symbols) { + String[] strings = null; + switch (field) { + case ERA: + strings = symbols.getEras(); + break; + + case MONTH: + strings = (style == LONG) ? symbols.getMonths() : symbols.getShortMonths(); + break; + + case DAY_OF_WEEK: + strings = (style == LONG) ? symbols.getWeekdays() : symbols.getShortWeekdays(); + break; + + case AM_PM: + strings = symbols.getAmPmStrings(); + break; + } + return strings; + } + + /** + * Fills in any unset fields in the calendar fields. First, the {@link + * #computeTime()} method is called if the time value (millisecond offset + * from the Epoch) has not been calculated from + * calendar field values. Then, the {@link #computeFields()} method is + * called to calculate all calendar field values. + */ + protected void complete() + { + if (!isTimeSet) + updateTime(); + if (!areFieldsSet || !areAllFieldsSet) { + computeFields(); // fills in unset fields + areAllFieldsSet = areFieldsSet = true; + } + } + + /** + * Returns whether the value of the specified calendar field has been set + * externally by calling one of the setter methods rather than by the + * internal time calculation. + * + * @return true if the field has been set externally, + * false otherwise. + * @exception IndexOutOfBoundsException if the specified + * field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #selectFields() + * @see #setFieldsComputed(int) + */ + final boolean isExternallySet(int field) { + return stamp[field] >= MINIMUM_USER_STAMP; + } + + /** + * Returns a field mask (bit mask) indicating all calendar fields that + * have the state of externally or internally set. + * + * @return a bit mask indicating set state fields + */ + final int getSetStateFields() { + int mask = 0; + for (int i = 0; i < fields.length; i++) { + if (stamp[i] != UNSET) { + mask |= 1 << i; + } + } + return mask; + } + + /** + * Sets the state of the specified calendar fields to + * computed. This state means that the specified calendar fields + * have valid values that have been set by internal time calculation + * rather than by calling one of the setter methods. + * + * @param fieldMask the field to be marked as computed. + * @exception IndexOutOfBoundsException if the specified + * field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #isExternallySet(int) + * @see #selectFields() + */ + final void setFieldsComputed(int fieldMask) { + if (fieldMask == ALL_FIELDS) { + for (int i = 0; i < fields.length; i++) { + stamp[i] = COMPUTED; + isSet[i] = true; + } + areFieldsSet = areAllFieldsSet = true; + } else { + for (int i = 0; i < fields.length; i++) { + if ((fieldMask & 1) == 1) { + stamp[i] = COMPUTED; + isSet[i] = true; + } else { + if (areAllFieldsSet && !isSet[i]) { + areAllFieldsSet = false; + } + } + fieldMask >>>= 1; + } + } + } + + /** + * Sets the state of the calendar fields that are not specified + * by fieldMask to unset. If fieldMask + * specifies all the calendar fields, then the state of this + * Calendar becomes that all the calendar fields are in sync + * with the time value (millisecond offset from the Epoch). + * + * @param fieldMask the field mask indicating which calendar fields are in + * sync with the time value. + * @exception IndexOutOfBoundsException if the specified + * field is out of range + * (field < 0 || field >= FIELD_COUNT). + * @see #isExternallySet(int) + * @see #selectFields() + */ + final void setFieldsNormalized(int fieldMask) { + if (fieldMask != ALL_FIELDS) { + for (int i = 0; i < fields.length; i++) { + if ((fieldMask & 1) == 0) { + stamp[i] = fields[i] = 0; // UNSET == 0 + isSet[i] = false; + } + fieldMask >>= 1; + } + } + + // Some or all of the fields are in sync with the + // milliseconds, but the stamp values are not normalized yet. + areFieldsSet = true; + areAllFieldsSet = false; + } + + /** + * Returns whether the calendar fields are partially in sync with the time + * value or fully in sync but not stamp values are not normalized yet. + */ + final boolean isPartiallyNormalized() { + return areFieldsSet && !areAllFieldsSet; + } + + /** + * Returns whether the calendar fields are fully in sync with the time + * value. + */ + final boolean isFullyNormalized() { + return areFieldsSet && areAllFieldsSet; + } + + /** + * Marks this Calendar as not sync'd. + */ + final void setUnnormalized() { + areFieldsSet = areAllFieldsSet = false; + } + + /** + * Returns whether the specified field is on in the + * fieldMask. + */ + static final boolean isFieldSet(int fieldMask, int field) { + return (fieldMask & (1 << field)) != 0; + } + + /** + * Returns a field mask indicating which calendar field values + * to be used to calculate the time value. The calendar fields are + * returned as a bit mask, each bit of which corresponds to a field, i.e., + * the mask value of field is (1 << + * field). For example, 0x26 represents the YEAR, + * MONTH, and DAY_OF_MONTH fields (i.e., 0x26 is + * equal to + * (1<<YEAR)|(1<<MONTH)|(1<<DAY_OF_MONTH)). + * + *

This method supports the calendar fields resolution as described in + * the class description. If the bit mask for a given field is on and its + * field has not been set (i.e., isSet(field) is + * false), then the default value of the field has to be + * used, which case means that the field has been selected because the + * selected combination involves the field. + * + * @return a bit mask of selected fields + * @see #isExternallySet(int) + * @see #setInternallySetState(int) + */ + final int selectFields() { + // This implementation has been taken from the GregorianCalendar class. + + // The YEAR field must always be used regardless of its SET + // state because YEAR is a mandatory field to determine the date + // and the default value (EPOCH_YEAR) may change through the + // normalization process. + int fieldMask = YEAR_MASK; + + if (stamp[ERA] != UNSET) { + fieldMask |= ERA_MASK; + } + // Find the most recent group of fields specifying the day within + // the year. These may be any of the following combinations: + // MONTH + DAY_OF_MONTH + // MONTH + WEEK_OF_MONTH + DAY_OF_WEEK + // MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK + // DAY_OF_YEAR + // WEEK_OF_YEAR + DAY_OF_WEEK + // We look for the most recent of the fields in each group to determine + // the age of the group. For groups involving a week-related field such + // as WEEK_OF_MONTH, DAY_OF_WEEK_IN_MONTH, or WEEK_OF_YEAR, both the + // week-related field and the DAY_OF_WEEK must be set for the group as a + // whole to be considered. (See bug 4153860 - liu 7/24/98.) + int dowStamp = stamp[DAY_OF_WEEK]; + int monthStamp = stamp[MONTH]; + int domStamp = stamp[DAY_OF_MONTH]; + int womStamp = aggregateStamp(stamp[WEEK_OF_MONTH], dowStamp); + int dowimStamp = aggregateStamp(stamp[DAY_OF_WEEK_IN_MONTH], dowStamp); + int doyStamp = stamp[DAY_OF_YEAR]; + int woyStamp = aggregateStamp(stamp[WEEK_OF_YEAR], dowStamp); + + int bestStamp = domStamp; + if (womStamp > bestStamp) { + bestStamp = womStamp; + } + if (dowimStamp > bestStamp) { + bestStamp = dowimStamp; + } + if (doyStamp > bestStamp) { + bestStamp = doyStamp; + } + if (woyStamp > bestStamp) { + bestStamp = woyStamp; + } + + /* No complete combination exists. Look for WEEK_OF_MONTH, + * DAY_OF_WEEK_IN_MONTH, or WEEK_OF_YEAR alone. Treat DAY_OF_WEEK alone + * as DAY_OF_WEEK_IN_MONTH. + */ + if (bestStamp == UNSET) { + womStamp = stamp[WEEK_OF_MONTH]; + dowimStamp = Math.max(stamp[DAY_OF_WEEK_IN_MONTH], dowStamp); + woyStamp = stamp[WEEK_OF_YEAR]; + bestStamp = Math.max(Math.max(womStamp, dowimStamp), woyStamp); + + /* Treat MONTH alone or no fields at all as DAY_OF_MONTH. This may + * result in bestStamp = domStamp = UNSET if no fields are set, + * which indicates DAY_OF_MONTH. + */ + if (bestStamp == UNSET) { + bestStamp = domStamp = monthStamp; + } + } + + if (bestStamp == domStamp || + (bestStamp == womStamp && stamp[WEEK_OF_MONTH] >= stamp[WEEK_OF_YEAR]) || + (bestStamp == dowimStamp && stamp[DAY_OF_WEEK_IN_MONTH] >= stamp[WEEK_OF_YEAR])) { + fieldMask |= MONTH_MASK; + if (bestStamp == domStamp) { + fieldMask |= DAY_OF_MONTH_MASK; + } else { + assert (bestStamp == womStamp || bestStamp == dowimStamp); + if (dowStamp != UNSET) { + fieldMask |= DAY_OF_WEEK_MASK; + } + if (womStamp == dowimStamp) { + // When they are equal, give the priority to + // WEEK_OF_MONTH for compatibility. + if (stamp[WEEK_OF_MONTH] >= stamp[DAY_OF_WEEK_IN_MONTH]) { + fieldMask |= WEEK_OF_MONTH_MASK; + } else { + fieldMask |= DAY_OF_WEEK_IN_MONTH_MASK; + } + } else { + if (bestStamp == womStamp) { + fieldMask |= WEEK_OF_MONTH_MASK; + } else { + assert (bestStamp == dowimStamp); + if (stamp[DAY_OF_WEEK_IN_MONTH] != UNSET) { + fieldMask |= DAY_OF_WEEK_IN_MONTH_MASK; + } + } + } + } + } else { + assert (bestStamp == doyStamp || bestStamp == woyStamp || + bestStamp == UNSET); + if (bestStamp == doyStamp) { + fieldMask |= DAY_OF_YEAR_MASK; + } else { + assert (bestStamp == woyStamp); + if (dowStamp != UNSET) { + fieldMask |= DAY_OF_WEEK_MASK; + } + fieldMask |= WEEK_OF_YEAR_MASK; + } + } + + // Find the best set of fields specifying the time of day. There + // are only two possibilities here; the HOUR_OF_DAY or the + // AM_PM and the HOUR. + int hourOfDayStamp = stamp[HOUR_OF_DAY]; + int hourStamp = aggregateStamp(stamp[HOUR], stamp[AM_PM]); + bestStamp = (hourStamp > hourOfDayStamp) ? hourStamp : hourOfDayStamp; + + // if bestStamp is still UNSET, then take HOUR or AM_PM. (See 4846659) + if (bestStamp == UNSET) { + bestStamp = Math.max(stamp[HOUR], stamp[AM_PM]); + } + + // Hours + if (bestStamp != UNSET) { + if (bestStamp == hourOfDayStamp) { + fieldMask |= HOUR_OF_DAY_MASK; + } else { + fieldMask |= HOUR_MASK; + if (stamp[AM_PM] != UNSET) { + fieldMask |= AM_PM_MASK; + } + } + } + if (stamp[MINUTE] != UNSET) { + fieldMask |= MINUTE_MASK; + } + if (stamp[SECOND] != UNSET) { + fieldMask |= SECOND_MASK; + } + if (stamp[MILLISECOND] != UNSET) { + fieldMask |= MILLISECOND_MASK; + } + if (stamp[ZONE_OFFSET] >= MINIMUM_USER_STAMP) { + fieldMask |= ZONE_OFFSET_MASK; + } + if (stamp[DST_OFFSET] >= MINIMUM_USER_STAMP) { + fieldMask |= DST_OFFSET_MASK; + } + + return fieldMask; + } + + /** + * Returns the pseudo-time-stamp for two fields, given their + * individual pseudo-time-stamps. If either of the fields + * is unset, then the aggregate is unset. Otherwise, the + * aggregate is the later of the two stamps. + */ + private static final int aggregateStamp(int stamp_a, int stamp_b) { + if (stamp_a == UNSET || stamp_b == UNSET) { + return UNSET; + } + return (stamp_a > stamp_b) ? stamp_a : stamp_b; + } + + /** + * Compares this Calendar to the specified + * Object. The result is true if and only if + * the argument is a Calendar object of the same calendar + * system that represents the same time value (millisecond offset from the + * Epoch) under the same + * Calendar parameters as this object. + * + *

The Calendar parameters are the values represented + * by the isLenient, getFirstDayOfWeek, + * getMinimalDaysInFirstWeek and getTimeZone + * methods. If there is any difference in those parameters + * between the two Calendars, this method returns + * false. + * + *

Use the {@link #compareTo(Calendar) compareTo} method to + * compare only the time values. + * + * @param obj the object to compare with. + * @return true if this object is equal to obj; + * false otherwise. + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + try { + Calendar that = (Calendar)obj; + return compareTo(getMillisOf(that)) == 0 && + lenient == that.lenient && + firstDayOfWeek == that.firstDayOfWeek && + minimalDaysInFirstWeek == that.minimalDaysInFirstWeek && + zone.equals(that.zone); + } catch (Exception e) { + // Note: GregorianCalendar.computeTime throws + // IllegalArgumentException if the ERA value is invalid + // even it's in lenient mode. + } + return false; + } + + /** + * Returns a hash code for this calendar. + * + * @return a hash code value for this object. + * @since 1.2 + */ + public int hashCode() { + // 'otheritems' represents the hash code for the previous versions. + int otheritems = (lenient ? 1 : 0) + | (firstDayOfWeek << 1) + | (minimalDaysInFirstWeek << 4) + | (zone.hashCode() << 7); + long t = getMillisOf(this); + return (int) t ^ (int)(t >> 32) ^ otheritems; + } + + /** + * Returns whether this Calendar represents a time + * before the time represented by the specified + * Object. This method is equivalent to: + *

+ * compareTo(when) < 0 + *
+ * if and only if when is a Calendar + * instance. Otherwise, the method returns false. + * + * @param when the Object to be compared + * @return true if the time of this + * Calendar is before the time represented by + * when; false otherwise. + * @see #compareTo(Calendar) + */ + public boolean before(Object when) { + return when instanceof Calendar + && compareTo((Calendar)when) < 0; + } + + /** + * Returns whether this Calendar represents a time + * after the time represented by the specified + * Object. This method is equivalent to: + *
+ * compareTo(when) > 0 + *
+ * if and only if when is a Calendar + * instance. Otherwise, the method returns false. + * + * @param when the Object to be compared + * @return true if the time of this Calendar is + * after the time represented by when; false + * otherwise. + * @see #compareTo(Calendar) + */ + public boolean after(Object when) { + return when instanceof Calendar + && compareTo((Calendar)when) > 0; + } + + /** + * Compares the time values (millisecond offsets from the Epoch) represented by two + * Calendar objects. + * + * @param anotherCalendar the Calendar to be compared. + * @return the value 0 if the time represented by the argument + * is equal to the time represented by this Calendar; a value + * less than 0 if the time of this Calendar is + * before the time represented by the argument; and a value greater than + * 0 if the time of this Calendar is after the + * time represented by the argument. + * @exception NullPointerException if the specified Calendar is + * null. + * @exception IllegalArgumentException if the time value of the + * specified Calendar object can't be obtained due to + * any invalid calendar values. + * @since 1.5 + */ + public int compareTo(Calendar anotherCalendar) { + return compareTo(getMillisOf(anotherCalendar)); + } + + /** + * Adds or subtracts the specified amount of time to the given calendar field, + * based on the calendar's rules. For example, to subtract 5 days from + * the current time of the calendar, you can achieve it by calling: + *

add(Calendar.DAY_OF_MONTH, -5). + * + * @param field the calendar field. + * @param amount the amount of date or time to be added to the field. + * @see #roll(int,int) + * @see #set(int,int) + */ + abstract public void add(int field, int amount); + + /** + * Adds or subtracts (up/down) a single unit of time on the given time + * field without changing larger fields. For example, to roll the current + * date up by one day, you can achieve it by calling: + *

roll(Calendar.DATE, true). + * When rolling on the year or Calendar.YEAR field, it will roll the year + * value in the range between 1 and the value returned by calling + * getMaximum(Calendar.YEAR). + * When rolling on the month or Calendar.MONTH field, other fields like + * date might conflict and, need to be changed. For instance, + * rolling the month on the date 01/31/96 will result in 02/29/96. + * When rolling on the hour-in-day or Calendar.HOUR_OF_DAY field, it will + * roll the hour value in the range between 0 and 23, which is zero-based. + * + * @param field the time field. + * @param up indicates if the value of the specified time field is to be + * rolled up or rolled down. Use true if rolling up, false otherwise. + * @see Calendar#add(int,int) + * @see Calendar#set(int,int) + */ + abstract public void roll(int field, boolean up); + + /** + * Adds the specified (signed) amount to the specified calendar field + * without changing larger fields. A negative amount means to roll + * down. + * + *

NOTE: This default implementation on Calendar just repeatedly calls the + * version of {@link #roll(int,boolean) roll()} that rolls by one unit. This may not + * always do the right thing. For example, if the DAY_OF_MONTH field is 31, + * rolling through February will leave it set to 28. The GregorianCalendar + * version of this function takes care of this problem. Other subclasses + * should also provide overrides of this function that do the right thing. + * + * @param field the calendar field. + * @param amount the signed amount to add to the calendar field. + * @since 1.2 + * @see #roll(int,boolean) + * @see #add(int,int) + * @see #set(int,int) + */ + public void roll(int field, int amount) + { + while (amount > 0) { + roll(field, true); + amount--; + } + while (amount < 0) { + roll(field, false); + amount++; + } + } + + /** + * Sets the time zone with the given time zone value. + * + * @param value the given time zone. + */ + public void setTimeZone(TimeZone value) + { + zone = value; + sharedZone = false; + /* Recompute the fields from the time using the new zone. This also + * works if isTimeSet is false (after a call to set()). In that case + * the time will be computed from the fields using the new zone, then + * the fields will get recomputed from that. Consider the sequence of + * calls: cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST). + * Is cal set to 1 o'clock EST or 1 o'clock PST? Answer: PST. More + * generally, a call to setTimeZone() affects calls to set() BEFORE AND + * AFTER it up to the next call to complete(). + */ + areAllFieldsSet = areFieldsSet = false; + } + + /** + * Gets the time zone. + * + * @return the time zone object associated with this calendar. + */ + public TimeZone getTimeZone() + { + // If the TimeZone object is shared by other Calendar instances, then + // create a clone. + if (sharedZone) { + zone = (TimeZone) zone.clone(); + sharedZone = false; + } + return zone; + } + + /** + * Returns the time zone (without cloning). + */ + TimeZone getZone() { + return zone; + } + + /** + * Sets the sharedZone flag to shared. + */ + void setZoneShared(boolean shared) { + sharedZone = shared; + } + + /** + * Specifies whether or not date/time interpretation is to be lenient. With + * lenient interpretation, a date such as "February 942, 1996" will be + * treated as being equivalent to the 941st day after February 1, 1996. + * With strict (non-lenient) interpretation, such dates will cause an exception to be + * thrown. The default is lenient. + * + * @param lenient true if the lenient mode is to be turned + * on; false if it is to be turned off. + * @see #isLenient() + * @see java.text.DateFormat#setLenient + */ + public void setLenient(boolean lenient) + { + this.lenient = lenient; + } + + /** + * Tells whether date/time interpretation is to be lenient. + * + * @return true if the interpretation mode of this calendar is lenient; + * false otherwise. + * @see #setLenient(boolean) + */ + public boolean isLenient() + { + return lenient; + } + + /** + * Sets what the first day of the week is; e.g., SUNDAY in the U.S., + * MONDAY in France. + * + * @param value the given first day of the week. + * @see #getFirstDayOfWeek() + * @see #getMinimalDaysInFirstWeek() + */ + public void setFirstDayOfWeek(int value) + { + if (firstDayOfWeek == value) { + return; + } + firstDayOfWeek = value; + invalidateWeekFields(); + } + + /** + * Gets what the first day of the week is; e.g., SUNDAY in the U.S., + * MONDAY in France. + * + * @return the first day of the week. + * @see #setFirstDayOfWeek(int) + * @see #getMinimalDaysInFirstWeek() + */ + public int getFirstDayOfWeek() + { + return firstDayOfWeek; + } + + /** + * Sets what the minimal days required in the first week of the year are; + * For example, if the first week is defined as one that contains the first + * day of the first month of a year, call this method with value 1. If it + * must be a full week, use value 7. + * + * @param value the given minimal days required in the first week + * of the year. + * @see #getMinimalDaysInFirstWeek() + */ + public void setMinimalDaysInFirstWeek(int value) + { + if (minimalDaysInFirstWeek == value) { + return; + } + minimalDaysInFirstWeek = value; + invalidateWeekFields(); + } + + /** + * Gets what the minimal days required in the first week of the year are; + * e.g., if the first week is defined as one that contains the first day + * of the first month of a year, this method returns 1. If + * the minimal days required must be a full week, this method + * returns 7. + * + * @return the minimal days required in the first week of the year. + * @see #setMinimalDaysInFirstWeek(int) + */ + public int getMinimalDaysInFirstWeek() + { + return minimalDaysInFirstWeek; + } + + /** + * Returns whether this {@code Calendar} supports week dates. + * + *

The default implementation of this method returns {@code false}. + * + * @return {@code true} if this {@code Calendar} supports week dates; + * {@code false} otherwise. + * @see #getWeekYear() + * @see #setWeekDate(int,int,int) + * @see #getWeeksInWeekYear() + * @since 1.7 + */ + public boolean isWeekDateSupported() { + return false; + } + + /** + * Returns the week year represented by this {@code Calendar}. The + * week year is in sync with the week cycle. The {@linkplain + * #getFirstDayOfWeek() first day of the first week} is the first + * day of the week year. + * + *

The default implementation of this method throws an + * {@link UnsupportedOperationException}. + * + * @return the week year of this {@code Calendar} + * @exception UnsupportedOperationException + * if any week year numbering isn't supported + * in this {@code Calendar}. + * @see #isWeekDateSupported() + * @see #getFirstDayOfWeek() + * @see #getMinimalDaysInFirstWeek() + * @since 1.7 + */ + public int getWeekYear() { + throw new UnsupportedOperationException(); + } + + /** + * Sets the date of this {@code Calendar} with the the given date + * specifiers - week year, week of year, and day of week. + * + *

Unlike the {@code set} method, all of the calendar fields + * and {@code time} values are calculated upon return. + * + *

If {@code weekOfYear} is out of the valid week-of-year range + * in {@code weekYear}, the {@code weekYear} and {@code + * weekOfYear} values are adjusted in lenient mode, or an {@code + * IllegalArgumentException} is thrown in non-lenient mode. + * + *

The default implementation of this method throws an + * {@code UnsupportedOperationException}. + * + * @param weekYear the week year + * @param weekOfYear the week number based on {@code weekYear} + * @param dayOfWeek the day of week value: one of the constants + * for the {@link #DAY_OF_WEEK} field: {@link + * #SUNDAY}, ..., {@link #SATURDAY}. + * @exception IllegalArgumentException + * if any of the given date specifiers is invalid + * or any of the calendar fields are inconsistent + * with the given date specifiers in non-lenient mode + * @exception UnsupportedOperationException + * if any week year numbering isn't supported in this + * {@code Calendar}. + * @see #isWeekDateSupported() + * @see #getFirstDayOfWeek() + * @see #getMinimalDaysInFirstWeek() + * @since 1.7 + */ + public void setWeekDate(int weekYear, int weekOfYear, int dayOfWeek) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the number of weeks in the week year represented by this + * {@code Calendar}. + * + *

The default implementation of this method throws an + * {@code UnsupportedOperationException}. + * + * @return the number of weeks in the week year. + * @exception UnsupportedOperationException + * if any week year numbering isn't supported in this + * {@code Calendar}. + * @see #WEEK_OF_YEAR + * @see #isWeekDateSupported() + * @see #getWeekYear() + * @see #getActualMaximum(int) + * @since 1.7 + */ + public int getWeeksInWeekYear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns the minimum value for the given calendar field of this + * Calendar instance. The minimum value is defined as + * the smallest value returned by the {@link #get(int) get} method + * for any possible time value. The minimum value depends on + * calendar system specific parameters of the instance. + * + * @param field the calendar field. + * @return the minimum value for the given calendar field. + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + abstract public int getMinimum(int field); + + /** + * Returns the maximum value for the given calendar field of this + * Calendar instance. The maximum value is defined as + * the largest value returned by the {@link #get(int) get} method + * for any possible time value. The maximum value depends on + * calendar system specific parameters of the instance. + * + * @param field the calendar field. + * @return the maximum value for the given calendar field. + * @see #getMinimum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + abstract public int getMaximum(int field); + + /** + * Returns the highest minimum value for the given calendar field + * of this Calendar instance. The highest minimum + * value is defined as the largest value returned by {@link + * #getActualMinimum(int)} for any possible time value. The + * greatest minimum value depends on calendar system specific + * parameters of the instance. + * + * @param field the calendar field. + * @return the highest minimum value for the given calendar field. + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + abstract public int getGreatestMinimum(int field); + + /** + * Returns the lowest maximum value for the given calendar field + * of this Calendar instance. The lowest maximum + * value is defined as the smallest value returned by {@link + * #getActualMaximum(int)} for any possible time value. The least + * maximum value depends on calendar system specific parameters of + * the instance. For example, a Calendar for the + * Gregorian calendar system returns 28 for the + * DAY_OF_MONTH field, because the 28th is the last + * day of the shortest month of this calendar, February in a + * common year. + * + * @param field the calendar field. + * @return the lowest maximum value for the given calendar field. + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getActualMinimum(int) + * @see #getActualMaximum(int) + */ + abstract public int getLeastMaximum(int field); + + /** + * Returns the minimum value that the specified calendar field + * could have, given the time value of this Calendar. + * + *

The default implementation of this method uses an iterative + * algorithm to determine the actual minimum value for the + * calendar field. Subclasses should, if possible, override this + * with a more efficient implementation - in many cases, they can + * simply return getMinimum(). + * + * @param field the calendar field + * @return the minimum of the given calendar field for the time + * value of this Calendar + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMaximum(int) + * @since 1.2 + */ + public int getActualMinimum(int field) { + int fieldValue = getGreatestMinimum(field); + int endValue = getMinimum(field); + + // if we know that the minimum value is always the same, just return it + if (fieldValue == endValue) { + return fieldValue; + } + + // clone the calendar so we don't mess with the real one, and set it to + // accept anything for the field values + Calendar work = (Calendar)this.clone(); + work.setLenient(true); + + // now try each value from getLeastMaximum() to getMaximum() one by one until + // we get a value that normalizes to another value. The last value that + // normalizes to itself is the actual minimum for the current date + int result = fieldValue; + + do { + work.set(field, fieldValue); + if (work.get(field) != fieldValue) { + break; + } else { + result = fieldValue; + fieldValue--; + } + } while (fieldValue >= endValue); + + return result; + } + + /** + * Returns the maximum value that the specified calendar field + * could have, given the time value of this + * Calendar. For example, the actual maximum value of + * the MONTH field is 12 in some years, and 13 in + * other years in the Hebrew calendar system. + * + *

The default implementation of this method uses an iterative + * algorithm to determine the actual maximum value for the + * calendar field. Subclasses should, if possible, override this + * with a more efficient implementation. + * + * @param field the calendar field + * @return the maximum of the given calendar field for the time + * value of this Calendar + * @see #getMinimum(int) + * @see #getMaximum(int) + * @see #getGreatestMinimum(int) + * @see #getLeastMaximum(int) + * @see #getActualMinimum(int) + * @since 1.2 + */ + public int getActualMaximum(int field) { + int fieldValue = getLeastMaximum(field); + int endValue = getMaximum(field); + + // if we know that the maximum value is always the same, just return it. + if (fieldValue == endValue) { + return fieldValue; + } + + // clone the calendar so we don't mess with the real one, and set it to + // accept anything for the field values. + Calendar work = (Calendar)this.clone(); + work.setLenient(true); + + // if we're counting weeks, set the day of the week to Sunday. We know the + // last week of a month or year will contain the first day of the week. + if (field == WEEK_OF_YEAR || field == WEEK_OF_MONTH) + work.set(DAY_OF_WEEK, firstDayOfWeek); + + // now try each value from getLeastMaximum() to getMaximum() one by one until + // we get a value that normalizes to another value. The last value that + // normalizes to itself is the actual maximum for the current date + int result = fieldValue; + + do { + work.set(field, fieldValue); + if (work.get(field) != fieldValue) { + break; + } else { + result = fieldValue; + fieldValue++; + } + } while (fieldValue <= endValue); + + return result; + } + + /** + * Creates and returns a copy of this object. + * + * @return a copy of this object. + */ + public Object clone() + { + try { + Calendar other = (Calendar) super.clone(); + + other.fields = new int[FIELD_COUNT]; + other.isSet = new boolean[FIELD_COUNT]; + other.stamp = new int[FIELD_COUNT]; + for (int i = 0; i < FIELD_COUNT; i++) { + other.fields[i] = fields[i]; + other.stamp[i] = stamp[i]; + other.isSet[i] = isSet[i]; + } + other.zone = (TimeZone) zone.clone(); + return other; + } + catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + private static final String[] FIELD_NAME = { + "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH", "DAY_OF_MONTH", + "DAY_OF_YEAR", "DAY_OF_WEEK", "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", + "HOUR_OF_DAY", "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET", + "DST_OFFSET" + }; + + /** + * Returns the name of the specified calendar field. + * + * @param field the calendar field + * @return the calendar field name + * @exception IndexOutOfBoundsException if field is negative, + * equal to or greater then FIELD_COUNT. + */ + static final String getFieldName(int field) { + return FIELD_NAME[field]; + } + + /** + * Return a string representation of this calendar. This method + * is intended to be used only for debugging purposes, and the + * format of the returned string may vary between implementations. + * The returned string may be empty but may not be null. + * + * @return a string representation of this calendar. + */ + public String toString() { + // NOTE: BuddhistCalendar.toString() interprets the string + // produced by this method so that the Gregorian year number + // is substituted by its B.E. year value. It relies on + // "...,YEAR=,..." or "...,YEAR=?,...". + StringBuilder buffer = new StringBuilder(800); + buffer.append(getClass().getName()).append('['); + appendValue(buffer, "time", isTimeSet, time); + buffer.append(",areFieldsSet=").append(areFieldsSet); + buffer.append(",areAllFieldsSet=").append(areAllFieldsSet); + buffer.append(",lenient=").append(lenient); + buffer.append(",zone=").append(zone); + appendValue(buffer, ",firstDayOfWeek", true, (long) firstDayOfWeek); + appendValue(buffer, ",minimalDaysInFirstWeek", true, (long) minimalDaysInFirstWeek); + for (int i = 0; i < FIELD_COUNT; ++i) { + buffer.append(','); + appendValue(buffer, FIELD_NAME[i], isSet(i), (long) fields[i]); + } + buffer.append(']'); + return buffer.toString(); + } + + // =======================privates=============================== + + private static final void appendValue(StringBuilder sb, String item, boolean valid, long value) { + sb.append(item).append('='); + if (valid) { + sb.append(value); + } else { + sb.append('?'); + } + } + + /** + * Both firstDayOfWeek and minimalDaysInFirstWeek are locale-dependent. + * They are used to figure out the week count for a specific date for + * a given locale. These must be set when a Calendar is constructed. + * @param desiredLocale the given locale. + */ + private void setWeekCountData(Locale desiredLocale) + { + /* try to get the Locale data from the cache */ + int[] data = cachedLocaleData.get(desiredLocale); + if (data == null) { /* cache miss */ + ResourceBundle bundle = LocaleData.getCalendarData(desiredLocale); + data = new int[2]; + data[0] = Integer.parseInt(bundle.getString("firstDayOfWeek")); + data[1] = Integer.parseInt(bundle.getString("minimalDaysInFirstWeek")); + cachedLocaleData.putIfAbsent(desiredLocale, data); + } + firstDayOfWeek = data[0]; + minimalDaysInFirstWeek = data[1]; + } + + /** + * Recomputes the time and updates the status fields isTimeSet + * and areFieldsSet. Callers should check isTimeSet and only + * call this method if isTimeSet is false. + */ + private void updateTime() { + computeTime(); + // The areFieldsSet and areAllFieldsSet values are no longer + // controlled here (as of 1.5). + isTimeSet = true; + } + + private int compareTo(long t) { + long thisTime = getMillisOf(this); + return (thisTime > t) ? 1 : (thisTime == t) ? 0 : -1; + } + + private static final long getMillisOf(Calendar calendar) { + if (calendar.isTimeSet) { + return calendar.time; + } + Calendar cal = (Calendar) calendar.clone(); + cal.setLenient(true); + return cal.getTimeInMillis(); + } + + /** + * Adjusts the stamp[] values before nextStamp overflow. nextStamp + * is set to the next stamp value upon the return. + */ + private final void adjustStamp() { + int max = MINIMUM_USER_STAMP; + int newStamp = MINIMUM_USER_STAMP; + + for (;;) { + int min = Integer.MAX_VALUE; + for (int i = 0; i < stamp.length; i++) { + int v = stamp[i]; + if (v >= newStamp && min > v) { + min = v; + } + if (max < v) { + max = v; + } + } + if (max != min && min == Integer.MAX_VALUE) { + break; + } + for (int i = 0; i < stamp.length; i++) { + if (stamp[i] == min) { + stamp[i] = newStamp; + } + } + newStamp++; + if (min == max) { + break; + } + } + nextStamp = newStamp; + } + + /** + * Sets the WEEK_OF_MONTH and WEEK_OF_YEAR fields to new values with the + * new parameter value if they have been calculated internally. + */ + private void invalidateWeekFields() + { + if (stamp[WEEK_OF_MONTH] != COMPUTED && + stamp[WEEK_OF_YEAR] != COMPUTED) { + return; + } + + // We have to check the new values of these fields after changing + // firstDayOfWeek and/or minimalDaysInFirstWeek. If the field values + // have been changed, then set the new values. (4822110) + Calendar cal = (Calendar) clone(); + cal.setLenient(true); + cal.clear(WEEK_OF_MONTH); + cal.clear(WEEK_OF_YEAR); + + if (stamp[WEEK_OF_MONTH] == COMPUTED) { + int weekOfMonth = cal.get(WEEK_OF_MONTH); + if (fields[WEEK_OF_MONTH] != weekOfMonth) { + fields[WEEK_OF_MONTH] = weekOfMonth; + } + } + + if (stamp[WEEK_OF_YEAR] == COMPUTED) { + int weekOfYear = cal.get(WEEK_OF_YEAR); + if (fields[WEEK_OF_YEAR] != weekOfYear) { + fields[WEEK_OF_YEAR] = weekOfYear; + } + } + } + + /** + * Save the state of this object to a stream (i.e., serialize it). + * + * Ideally, Calendar would only write out its state data and + * the current time, and not write any field data out, such as + * fields[], isTimeSet, areFieldsSet, + * and isSet[]. nextStamp also should not be part + * of the persistent state. Unfortunately, this didn't happen before JDK 1.1 + * shipped. To be compatible with JDK 1.1, we will always have to write out + * the field values and state flags. However, nextStamp can be + * removed from the serialization stream; this will probably happen in the + * near future. + */ + private void writeObject(ObjectOutputStream stream) + throws IOException + { + // Try to compute the time correctly, for the future (stream + // version 2) in which we don't write out fields[] or isSet[]. + if (!isTimeSet) { + try { + updateTime(); + } + catch (IllegalArgumentException e) {} + } + + // If this Calendar has a ZoneInfo, save it and set a + // SimpleTimeZone equivalent (as a single DST schedule) for + // backward compatibility. + TimeZone savedZone = null; + if (zone instanceof ZoneInfo) { + SimpleTimeZone stz = ((ZoneInfo)zone).getLastRuleInstance(); + if (stz == null) { + stz = new SimpleTimeZone(zone.getRawOffset(), zone.getID()); + } + savedZone = zone; + zone = stz; + } + + // Write out the 1.1 FCS object. + stream.defaultWriteObject(); + + // Write out the ZoneInfo object + // 4802409: we write out even if it is null, a temporary workaround + // the real fix for bug 4844924 in corba-iiop + stream.writeObject(savedZone); + if (savedZone != null) { + zone = savedZone; + } + } + + private static class CalendarAccessControlContext { + private static final AccessControlContext INSTANCE; + static { + RuntimePermission perm = new RuntimePermission("accessClassInPackage.sun.util.calendar"); + PermissionCollection perms = perm.newPermissionCollection(); + perms.add(perm); + INSTANCE = new AccessControlContext(new ProtectionDomain[] { + new ProtectionDomain(null, perms) + }); + } + } + + /** + * Reconstitutes this object from a stream (i.e., deserialize it). + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + final ObjectInputStream input = stream; + input.defaultReadObject(); + + stamp = new int[FIELD_COUNT]; + + // Starting with version 2 (not implemented yet), we expect that + // fields[], isSet[], isTimeSet, and areFieldsSet may not be + // streamed out anymore. We expect 'time' to be correct. + if (serialVersionOnStream >= 2) + { + isTimeSet = true; + if (fields == null) fields = new int[FIELD_COUNT]; + if (isSet == null) isSet = new boolean[FIELD_COUNT]; + } + else if (serialVersionOnStream >= 0) + { + for (int i=0; i() { + public ZoneInfo run() throws Exception { + return (ZoneInfo) input.readObject(); + } + }, + CalendarAccessControlContext.INSTANCE); + } catch (PrivilegedActionException pae) { + Exception e = pae.getException(); + if (!(e instanceof OptionalDataException)) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else if (e instanceof IOException) { + throw (IOException) e; + } else if (e instanceof ClassNotFoundException) { + throw (ClassNotFoundException) e; + } + throw new RuntimeException(e); + } + } + if (zi != null) { + zone = zi; + } + + // If the deserialized object has a SimpleTimeZone, try to + // replace it with a ZoneInfo equivalent (as of 1.4) in order + // to be compatible with the SimpleTimeZone-based + // implementation as much as possible. + if (zone instanceof SimpleTimeZone) { + String id = zone.getID(); + TimeZone tz = TimeZone.getTimeZone(id); + if (tz != null && tz.hasSameRules(zone) && tz.getID().equals(id)) { + zone = tz; + } + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/Currency.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Currency.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,741 @@ +/* + * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.Serializable; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.logging.Level; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.spi.CurrencyNameProvider; +import java.util.spi.LocaleServiceProvider; +import sun.util.LocaleServiceProviderPool; +import sun.util.logging.PlatformLogger; +import sun.util.resources.LocaleData; +import sun.util.resources.OpenListResourceBundle; + + +/** + * Represents a currency. Currencies are identified by their ISO 4217 currency + * codes. Visit the + * ISO web site for more information, including a table of + * currency codes. + *

+ * The class is designed so that there's never more than one + * Currency instance for any given currency. Therefore, there's + * no public constructor. You obtain a Currency instance using + * the getInstance methods. + *

+ * Users can supersede the Java runtime currency data by creating a properties + * file named <JAVA_HOME>/lib/currency.properties. The contents + * of the properties file are key/value pairs of the ISO 3166 country codes + * and the ISO 4217 currency data respectively. The value part consists of + * three ISO 4217 values of a currency, i.e., an alphabetic code, a numeric + * code, and a minor unit. Those three ISO 4217 values are separated by commas. + * The lines which start with '#'s are considered comment lines. For example, + *

+ * + * #Sample currency properties
+ * JP=JPZ,999,0 + *
+ *

+ * will supersede the currency data for Japan. + * + * @since 1.4 + */ +public final class Currency implements Serializable { + + private static final long serialVersionUID = -158308464356906721L; + + /** + * ISO 4217 currency code for this currency. + * + * @serial + */ + private final String currencyCode; + + /** + * Default fraction digits for this currency. + * Set from currency data tables. + */ + transient private final int defaultFractionDigits; + + /** + * ISO 4217 numeric code for this currency. + * Set from currency data tables. + */ + transient private final int numericCode; + + + // class data: instance map + + private static HashMap instances = new HashMap(7); + private static HashSet available; + + + // Class data: currency data obtained from currency.data file. + // Purpose: + // - determine valid country codes + // - determine valid currency codes + // - map country codes to currency codes + // - obtain default fraction digits for currency codes + // + // sc = special case; dfd = default fraction digits + // Simple countries are those where the country code is a prefix of the + // currency code, and there are no known plans to change the currency. + // + // table formats: + // - mainTable: + // - maps country code to 32-bit int + // - 26*26 entries, corresponding to [A-Z]*[A-Z] + // - \u007F -> not valid country + // - bits 18-31: unused + // - bits 8-17: numeric code (0 to 1023) + // - bit 7: 1 - special case, bits 0-4 indicate which one + // 0 - simple country, bits 0-4 indicate final char of currency code + // - bits 5-6: fraction digits for simple countries, 0 for special cases + // - bits 0-4: final char for currency code for simple country, or ID of special case + // - special case IDs: + // - 0: country has no currency + // - other: index into sc* arrays + 1 + // - scCutOverTimes: cut-over time in millis as returned by + // System.currentTimeMillis for special case countries that are changing + // currencies; Long.MAX_VALUE for countries that are not changing currencies + // - scOldCurrencies: old currencies for special case countries + // - scNewCurrencies: new currencies for special case countries that are + // changing currencies; null for others + // - scOldCurrenciesDFD: default fraction digits for old currencies + // - scNewCurrenciesDFD: default fraction digits for new currencies, 0 for + // countries that are not changing currencies + // - otherCurrencies: concatenation of all currency codes that are not the + // main currency of a simple country, separated by "-" + // - otherCurrenciesDFD: decimal format digits for currencies in otherCurrencies, same order + + static int formatVersion; + static int dataVersion; + static int[] mainTable; + static long[] scCutOverTimes; + static String[] scOldCurrencies; + static String[] scNewCurrencies; + static int[] scOldCurrenciesDFD; + static int[] scNewCurrenciesDFD; + static int[] scOldCurrenciesNumericCode; + static int[] scNewCurrenciesNumericCode; + static String otherCurrencies; + static int[] otherCurrenciesDFD; + static int[] otherCurrenciesNumericCode; + + // handy constants - must match definitions in GenerateCurrencyData + // magic number + private static final int MAGIC_NUMBER = 0x43757244; + // number of characters from A to Z + private static final int A_TO_Z = ('Z' - 'A') + 1; + // entry for invalid country codes + private static final int INVALID_COUNTRY_ENTRY = 0x007F; + // entry for countries without currency + private static final int COUNTRY_WITHOUT_CURRENCY_ENTRY = 0x0080; + // mask for simple case country entries + private static final int SIMPLE_CASE_COUNTRY_MASK = 0x0000; + // mask for simple case country entry final character + private static final int SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK = 0x001F; + // mask for simple case country entry default currency digits + private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK = 0x0060; + // shift count for simple case country entry default currency digits + private static final int SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT = 5; + // mask for special case country entries + private static final int SPECIAL_CASE_COUNTRY_MASK = 0x0080; + // mask for special case country index + private static final int SPECIAL_CASE_COUNTRY_INDEX_MASK = 0x001F; + // delta from entry index component in main table to index into special case tables + private static final int SPECIAL_CASE_COUNTRY_INDEX_DELTA = 1; + // mask for distinguishing simple and special case countries + private static final int COUNTRY_TYPE_MASK = SIMPLE_CASE_COUNTRY_MASK | SPECIAL_CASE_COUNTRY_MASK; + // mask for the numeric code of the currency + private static final int NUMERIC_CODE_MASK = 0x0003FF00; + // shift count for the numeric code of the currency + private static final int NUMERIC_CODE_SHIFT = 8; + + // Currency data format version + private static final int VALID_FORMAT_VERSION = 1; + + static { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + String homeDir = System.getProperty("java.home"); + try { + String dataFile = homeDir + File.separator + + "lib" + File.separator + "currency.data"; + DataInputStream dis = new DataInputStream( + new BufferedInputStream( + new FileInputStream(dataFile))); + if (dis.readInt() != MAGIC_NUMBER) { + throw new InternalError("Currency data is possibly corrupted"); + } + formatVersion = dis.readInt(); + if (formatVersion != VALID_FORMAT_VERSION) { + throw new InternalError("Currency data format is incorrect"); + } + dataVersion = dis.readInt(); + mainTable = readIntArray(dis, A_TO_Z * A_TO_Z); + int scCount = dis.readInt(); + scCutOverTimes = readLongArray(dis, scCount); + scOldCurrencies = readStringArray(dis, scCount); + scNewCurrencies = readStringArray(dis, scCount); + scOldCurrenciesDFD = readIntArray(dis, scCount); + scNewCurrenciesDFD = readIntArray(dis, scCount); + scOldCurrenciesNumericCode = readIntArray(dis, scCount); + scNewCurrenciesNumericCode = readIntArray(dis, scCount); + int ocCount = dis.readInt(); + otherCurrencies = dis.readUTF(); + otherCurrenciesDFD = readIntArray(dis, ocCount); + otherCurrenciesNumericCode = readIntArray(dis, ocCount); + dis.close(); + } catch (IOException e) { + InternalError ie = new InternalError(); + ie.initCause(e); + throw ie; + } + + // look for the properties file for overrides + try { + File propFile = new File(homeDir + File.separator + + "lib" + File.separator + + "currency.properties"); + if (propFile.exists()) { + Properties props = new Properties(); + try (FileReader fr = new FileReader(propFile)) { + props.load(fr); + } + Set keys = props.stringPropertyNames(); + Pattern propertiesPattern = + Pattern.compile("([A-Z]{3})\\s*,\\s*(\\d{3})\\s*,\\s*([0-3])"); + for (String key : keys) { + replaceCurrencyData(propertiesPattern, + key.toUpperCase(Locale.ROOT), + props.getProperty(key).toUpperCase(Locale.ROOT)); + } + } + } catch (IOException e) { + info("currency.properties is ignored because of an IOException", e); + } + return null; + } + }); + } + + /** + * Constants for retrieving localized names from the name providers. + */ + private static final int SYMBOL = 0; + private static final int DISPLAYNAME = 1; + + + /** + * Constructs a Currency instance. The constructor is private + * so that we can insure that there's never more than one instance for a + * given currency. + */ + private Currency(String currencyCode, int defaultFractionDigits, int numericCode) { + this.currencyCode = currencyCode; + this.defaultFractionDigits = defaultFractionDigits; + this.numericCode = numericCode; + } + + /** + * Returns the Currency instance for the given currency code. + * + * @param currencyCode the ISO 4217 code of the currency + * @return the Currency instance for the given currency code + * @exception NullPointerException if currencyCode is null + * @exception IllegalArgumentException if currencyCode is not + * a supported ISO 4217 code. + */ + public static Currency getInstance(String currencyCode) { + return getInstance(currencyCode, Integer.MIN_VALUE, 0); + } + + private static Currency getInstance(String currencyCode, int defaultFractionDigits, + int numericCode) { + synchronized (instances) { + // Try to look up the currency code in the instances table. + // This does the null pointer check as a side effect. + // Also, if there already is an entry, the currencyCode must be valid. + Currency instance = instances.get(currencyCode); + if (instance != null) { + return instance; + } + + if (defaultFractionDigits == Integer.MIN_VALUE) { + // Currency code not internally generated, need to verify first + // A currency code must have 3 characters and exist in the main table + // or in the list of other currencies. + if (currencyCode.length() != 3) { + throw new IllegalArgumentException(); + } + char char1 = currencyCode.charAt(0); + char char2 = currencyCode.charAt(1); + int tableEntry = getMainTableEntry(char1, char2); + if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK + && tableEntry != INVALID_COUNTRY_ENTRY + && currencyCode.charAt(2) - 'A' == (tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK)) { + defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; + numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; + } else { + // Check for '-' separately so we don't get false hits in the table. + if (currencyCode.charAt(2) == '-') { + throw new IllegalArgumentException(); + } + int index = otherCurrencies.indexOf(currencyCode); + if (index == -1) { + throw new IllegalArgumentException(); + } + defaultFractionDigits = otherCurrenciesDFD[index / 4]; + numericCode = otherCurrenciesNumericCode[index / 4]; + } + } + + instance = new Currency(currencyCode, defaultFractionDigits, numericCode); + instances.put(currencyCode, instance); + return instance; + } + } + + /** + * Returns the Currency instance for the country of the + * given locale. The language and variant components of the locale + * are ignored. The result may vary over time, as countries change their + * currencies. For example, for the original member countries of the + * European Monetary Union, the method returns the old national currencies + * until December 31, 2001, and the Euro from January 1, 2002, local time + * of the respective countries. + *

+ * The method returns null for territories that don't + * have a currency, such as Antarctica. + * + * @param locale the locale for whose country a Currency + * instance is needed + * @return the Currency instance for the country of the given + * locale, or null + * @exception NullPointerException if locale or its country + * code is null + * @exception IllegalArgumentException if the country of the given locale + * is not a supported ISO 3166 country code. + */ + public static Currency getInstance(Locale locale) { + String country = locale.getCountry(); + if (country == null) { + throw new NullPointerException(); + } + + if (country.length() != 2) { + throw new IllegalArgumentException(); + } + + char char1 = country.charAt(0); + char char2 = country.charAt(1); + int tableEntry = getMainTableEntry(char1, char2); + if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK + && tableEntry != INVALID_COUNTRY_ENTRY) { + char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A'); + int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; + int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; + StringBuffer sb = new StringBuffer(country); + sb.append(finalChar); + return getInstance(sb.toString(), defaultFractionDigits, numericCode); + } else { + // special cases + if (tableEntry == INVALID_COUNTRY_ENTRY) { + throw new IllegalArgumentException(); + } + if (tableEntry == COUNTRY_WITHOUT_CURRENCY_ENTRY) { + return null; + } else { + int index = (tableEntry & SPECIAL_CASE_COUNTRY_INDEX_MASK) - SPECIAL_CASE_COUNTRY_INDEX_DELTA; + if (scCutOverTimes[index] == Long.MAX_VALUE || System.currentTimeMillis() < scCutOverTimes[index]) { + return getInstance(scOldCurrencies[index], scOldCurrenciesDFD[index], + scOldCurrenciesNumericCode[index]); + } else { + return getInstance(scNewCurrencies[index], scNewCurrenciesDFD[index], + scNewCurrenciesNumericCode[index]); + } + } + } + } + + /** + * Gets the set of available currencies. The returned set of currencies + * contains all of the available currencies, which may include currencies + * that represent obsolete ISO 4217 codes. The set can be modified + * without affecting the available currencies in the runtime. + * + * @return the set of available currencies. If there is no currency + * available in the runtime, the returned set is empty. + * @since 1.7 + */ + public static Set getAvailableCurrencies() { + synchronized(Currency.class) { + if (available == null) { + available = new HashSet(256); + + // Add simple currencies first + for (char c1 = 'A'; c1 <= 'Z'; c1 ++) { + for (char c2 = 'A'; c2 <= 'Z'; c2 ++) { + int tableEntry = getMainTableEntry(c1, c2); + if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK + && tableEntry != INVALID_COUNTRY_ENTRY) { + char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A'); + int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; + int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; + StringBuilder sb = new StringBuilder(); + sb.append(c1); + sb.append(c2); + sb.append(finalChar); + available.add(getInstance(sb.toString(), defaultFractionDigits, numericCode)); + } + } + } + + // Now add other currencies + StringTokenizer st = new StringTokenizer(otherCurrencies, "-"); + while (st.hasMoreElements()) { + available.add(getInstance((String)st.nextElement())); + } + } + } + + return (Set) available.clone(); + } + + /** + * Gets the ISO 4217 currency code of this currency. + * + * @return the ISO 4217 currency code of this currency. + */ + public String getCurrencyCode() { + return currencyCode; + } + + /** + * Gets the symbol of this currency for the default locale. + * For example, for the US Dollar, the symbol is "$" if the default + * locale is the US, while for other locales it may be "US$". If no + * symbol can be determined, the ISO 4217 currency code is returned. + * + * @return the symbol of this currency for the default locale + */ + public String getSymbol() { + return getSymbol(Locale.getDefault(Locale.Category.DISPLAY)); + } + + /** + * Gets the symbol of this currency for the specified locale. + * For example, for the US Dollar, the symbol is "$" if the specified + * locale is the US, while for other locales it may be "US$". If no + * symbol can be determined, the ISO 4217 currency code is returned. + * + * @param locale the locale for which a display name for this currency is + * needed + * @return the symbol of this currency for the specified locale + * @exception NullPointerException if locale is null + */ + public String getSymbol(Locale locale) { + try { + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(CurrencyNameProvider.class); + + if (pool.hasProviders()) { + // Assuming that all the country locales include necessary currency + // symbols in the Java runtime's resources, so there is no need to + // examine whether Java runtime's currency resource bundle is missing + // names. Therefore, no resource bundle is provided for calling this + // method. + String symbol = pool.getLocalizedObject( + CurrencyNameGetter.INSTANCE, + locale, (OpenListResourceBundle)null, + currencyCode, SYMBOL); + if (symbol != null) { + return symbol; + } + } + + ResourceBundle bundle = LocaleData.getCurrencyNames(locale); + return bundle.getString(currencyCode); + } catch (MissingResourceException e) { + // use currency code as symbol of last resort + return currencyCode; + } + } + + /** + * Gets the default number of fraction digits used with this currency. + * For example, the default number of fraction digits for the Euro is 2, + * while for the Japanese Yen it's 0. + * In the case of pseudo-currencies, such as IMF Special Drawing Rights, + * -1 is returned. + * + * @return the default number of fraction digits used with this currency + */ + public int getDefaultFractionDigits() { + return defaultFractionDigits; + } + + /** + * Returns the ISO 4217 numeric code of this currency. + * + * @return the ISO 4217 numeric code of this currency + * @since 1.7 + */ + public int getNumericCode() { + return numericCode; + } + + /** + * Gets the name that is suitable for displaying this currency for + * the default locale. If there is no suitable display name found + * for the default locale, the ISO 4217 currency code is returned. + * + * @return the display name of this currency for the default locale + * @since 1.7 + */ + public String getDisplayName() { + return getDisplayName(Locale.getDefault(Locale.Category.DISPLAY)); + } + + /** + * Gets the name that is suitable for displaying this currency for + * the specified locale. If there is no suitable display name found + * for the specified locale, the ISO 4217 currency code is returned. + * + * @param locale the locale for which a display name for this currency is + * needed + * @return the display name of this currency for the specified locale + * @exception NullPointerException if locale is null + * @since 1.7 + */ + public String getDisplayName(Locale locale) { + try { + OpenListResourceBundle bundle = LocaleData.getCurrencyNames(locale); + String result = null; + String bundleKey = currencyCode.toLowerCase(Locale.ROOT); + + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(CurrencyNameProvider.class); + if (pool.hasProviders()) { + result = pool.getLocalizedObject( + CurrencyNameGetter.INSTANCE, + locale, bundleKey, bundle, currencyCode, DISPLAYNAME); + } + + if (result == null) { + result = bundle.getString(bundleKey); + } + + if (result != null) { + return result; + } + } catch (MissingResourceException e) { + // fall through + } + + // use currency code as symbol of last resort + return currencyCode; + } + + /** + * Returns the ISO 4217 currency code of this currency. + * + * @return the ISO 4217 currency code of this currency + */ + public String toString() { + return currencyCode; + } + + /** + * Resolves instances being deserialized to a single instance per currency. + */ + private Object readResolve() { + return getInstance(currencyCode); + } + + /** + * Gets the main table entry for the country whose country code consists + * of char1 and char2. + */ + private static int getMainTableEntry(char char1, char char2) { + if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') { + throw new IllegalArgumentException(); + } + return mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')]; + } + + /** + * Sets the main table entry for the country whose country code consists + * of char1 and char2. + */ + private static void setMainTableEntry(char char1, char char2, int entry) { + if (char1 < 'A' || char1 > 'Z' || char2 < 'A' || char2 > 'Z') { + throw new IllegalArgumentException(); + } + mainTable[(char1 - 'A') * A_TO_Z + (char2 - 'A')] = entry; + } + + /** + * Obtains a localized currency names from a CurrencyNameProvider + * implementation. + */ + private static class CurrencyNameGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final CurrencyNameGetter INSTANCE = new CurrencyNameGetter(); + + public String getObject(CurrencyNameProvider currencyNameProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 1; + int type = (Integer)params[0]; + + switch(type) { + case SYMBOL: + return currencyNameProvider.getSymbol(key, locale); + case DISPLAYNAME: + return currencyNameProvider.getDisplayName(key, locale); + default: + assert false; // shouldn't happen + } + + return null; + } + } + + private static int[] readIntArray(DataInputStream dis, int count) throws IOException { + int[] ret = new int[count]; + for (int i = 0; i < count; i++) { + ret[i] = dis.readInt(); + } + + return ret; + } + + private static long[] readLongArray(DataInputStream dis, int count) throws IOException { + long[] ret = new long[count]; + for (int i = 0; i < count; i++) { + ret[i] = dis.readLong(); + } + + return ret; + } + + private static String[] readStringArray(DataInputStream dis, int count) throws IOException { + String[] ret = new String[count]; + for (int i = 0; i < count; i++) { + ret[i] = dis.readUTF(); + } + + return ret; + } + + /** + * Replaces currency data found in the currencydata.properties file + * + * @param pattern regex pattern for the properties + * @param ctry country code + * @param data currency data. This is a comma separated string that + * consists of "three-letter alphabet code", "three-digit numeric code", + * and "one-digit (0,1,2, or 3) default fraction digit". + * For example, "JPZ,392,0". + * @throws + */ + private static void replaceCurrencyData(Pattern pattern, String ctry, String curdata) { + + if (ctry.length() != 2) { + // ignore invalid country code + String message = new StringBuilder() + .append("The entry in currency.properties for ") + .append(ctry).append(" is ignored because of the invalid country code.") + .toString(); + info(message, null); + return; + } + + Matcher m = pattern.matcher(curdata); + if (!m.find()) { + // format is not recognized. ignore the data + String message = new StringBuilder() + .append("The entry in currency.properties for ") + .append(ctry) + .append(" is ignored because the value format is not recognized.") + .toString(); + info(message, null); + return; + } + + String code = m.group(1); + int numeric = Integer.parseInt(m.group(2)); + int fraction = Integer.parseInt(m.group(3)); + int entry = numeric << NUMERIC_CODE_SHIFT; + + int index; + for (index = 0; index < scOldCurrencies.length; index++) { + if (scOldCurrencies[index].equals(code)) { + break; + } + } + + if (index == scOldCurrencies.length) { + // simple case + entry |= (fraction << SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT) | + (code.charAt(2) - 'A'); + } else { + // special case + entry |= SPECIAL_CASE_COUNTRY_MASK | + (index + SPECIAL_CASE_COUNTRY_INDEX_DELTA); + } + setMainTableEntry(ctry.charAt(0), ctry.charAt(1), entry); + } + + private static void info(String message, Throwable t) { + PlatformLogger logger = PlatformLogger.getLogger("java.util.Currency"); + if (logger.isLoggable(PlatformLogger.INFO)) { + if (t != null) { + logger.info(message, t); + } else { + logger.info(message); + } + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/CurrencyData.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/CurrencyData.properties Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,586 @@ +# +# Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +formatVersion=1 + +# Version of the currency code information in this class. +# It is a serial number that accompanies with each amendment, such as +# 'MAxxx.doc' + +dataVersion=140 + +# List of all valid ISO 4217 currency codes. +# To ensure compatibility, do not remove codes. + +all=ADP020-AED784-AFA004-AFN971-ALL008-AMD051-ANG532-AOA973-ARS032-ATS040-AUD036-\ + AWG533-AYM945-AZM031-AZN944-BAM977-BBD052-BDT050-BEF056-BGL100-BGN975-BHD048-BIF108-\ + BMD060-BND096-BOB068-BOV984-BRL986-BSD044-BTN064-BWP072-BYB112-BYR974-\ + BZD084-CAD124-CDF976-CHF756-CLF990-CLP152-CNY156-COP170-CRC188-CSD891-CUP192-\ + CVE132-CYP196-CZK203-DEM276-DJF262-DKK208-DOP214-DZD012-EEK233-EGP818-\ + ERN232-ESP724-ETB230-EUR978-FIM246-FJD242-FKP238-FRF250-GBP826-GEL981-\ + GHC288-GHS936-GIP292-GMD270-GNF324-GRD300-GTQ320-GWP624-GYD328-HKD344-HNL340-\ + HRK191-HTG332-HUF348-IDR360-IEP372-ILS376-INR356-IQD368-IRR364-ISK352-\ + ITL380-JMD388-JOD400-JPY392-KES404-KGS417-KHR116-KMF174-KPW408-KRW410-\ + KWD414-KYD136-KZT398-LAK418-LBP422-LKR144-LRD430-LSL426-LTL440-LUF442-\ + LVL428-LYD434-MAD504-MDL498-MGA969-MGF450-MKD807-MMK104-MNT496-MOP446-MRO478-\ + MTL470-MUR480-MVR462-MWK454-MXN484-MXV979-MYR458-MZM508-MZN943-NAD516-NGN566-\ + NIO558-NLG528-NOK578-NPR524-NZD554-OMR512-PAB590-PEN604-PGK598-PHP608-\ + PKR586-PLN985-PTE620-PYG600-QAR634-ROL946-RON946-RSD941-RUB643-RUR810-RWF646-SAR682-\ + SBD090-SCR690-SDD736-SDG938-SEK752-SGD702-SHP654-SIT705-SKK703-SLL694-SOS706-\ + SRD968-SRG740-STD678-SVC222-SYP760-SZL748-THB764-TJS972-TMM795-TND788-TOP776-\ + TPE626-TRL792-TRY949-TTD780-TWD901-TZS834-UAH980-UGX800-USD840-USN997-USS998-\ + UYU858-UZS860-VEB862-VEF937-VND704-VUV548-WST882-XAF950-XAG961-XAU959-XBA955-\ + XBB956-XBC957-XBD958-XCD951-XDR960-XFO000-XFU000-XOF952-XPD964-XPF953-\ + XPT962-XTS963-XXX999-YER886-YUM891-ZAR710-ZMK894-ZWD716-ZWN942 + + +# Mappings from ISO 3166 country codes to ISO 4217 currency codes. +# +# Three forms are used: +# Form 1: = +# Form 2: =;

+ * Prior to JDK 1.1, the class Date had two additional + * functions. It allowed the interpretation of dates as year, month, day, hour, + * minute, and second values. It also allowed the formatting and parsing + * of date strings. Unfortunately, the API for these functions was not + * amenable to internationalization. As of JDK 1.1, the + * Calendar class should be used to convert between dates and time + * fields and the DateFormat class should be used to format and + * parse date strings. + * The corresponding methods in Date are deprecated. + *

+ * Although the Date class is intended to reflect + * coordinated universal time (UTC), it may not do so exactly, + * depending on the host environment of the Java Virtual Machine. + * Nearly all modern operating systems assume that 1 day = + * 24 × 60 × 60 = 86400 seconds + * in all cases. In UTC, however, about once every year or two there + * is an extra second, called a "leap second." The leap + * second is always added as the last second of the day, and always + * on December 31 or June 30. For example, the last minute of the + * year 1995 was 61 seconds long, thanks to an added leap second. + * Most computer clocks are not accurate enough to be able to reflect + * the leap-second distinction. + *

+ * Some computer standards are defined in terms of Greenwich mean + * time (GMT), which is equivalent to universal time (UT). GMT is + * the "civil" name for the standard; UT is the + * "scientific" name for the same standard. The + * distinction between UTC and UT is that UTC is based on an atomic + * clock and UT is based on astronomical observations, which for all + * practical purposes is an invisibly fine hair to split. Because the + * earth's rotation is not uniform (it slows down and speeds up + * in complicated ways), UT does not always flow uniformly. Leap + * seconds are introduced as needed into UTC so as to keep UTC within + * 0.9 seconds of UT1, which is a version of UT with certain + * corrections applied. There are other time and date systems as + * well; for example, the time scale used by the satellite-based + * global positioning system (GPS) is synchronized to UTC but is + * not adjusted for leap seconds. An interesting source of + * further information is the U.S. Naval Observatory, particularly + * the Directorate of Time at: + *

+ *     http://tycho.usno.navy.mil
+ * 
+ *

+ * and their definitions of "Systems of Time" at: + *

+ *     http://tycho.usno.navy.mil/systime.html
+ * 
+ *

+ * In all methods of class Date that accept or return + * year, month, date, hours, minutes, and seconds values, the + * following representations are used: + *

    + *
  • A year y is represented by the integer + * y - 1900. + *
  • A month is represented by an integer from 0 to 11; 0 is January, + * 1 is February, and so forth; thus 11 is December. + *
  • A date (day of month) is represented by an integer from 1 to 31 + * in the usual manner. + *
  • An hour is represented by an integer from 0 to 23. Thus, the hour + * from midnight to 1 a.m. is hour 0, and the hour from noon to 1 + * p.m. is hour 12. + *
  • A minute is represented by an integer from 0 to 59 in the usual manner. + *
  • A second is represented by an integer from 0 to 61; the values 60 and + * 61 occur only for leap seconds and even then only in Java + * implementations that actually track leap seconds correctly. Because + * of the manner in which leap seconds are currently introduced, it is + * extremely unlikely that two leap seconds will occur in the same + * minute, but this specification follows the date and time conventions + * for ISO C. + *
+ *

+ * In all cases, arguments given to methods for these purposes need + * not fall within the indicated ranges; for example, a date may be + * specified as January 32 and is interpreted as meaning February 1. + * + * @author James Gosling + * @author Arthur van Hoff + * @author Alan Liu + * @see java.text.DateFormat + * @see java.util.Calendar + * @see java.util.TimeZone + * @since JDK1.0 + */ +public class Date + implements java.io.Serializable, Cloneable, Comparable +{ + private static final BaseCalendar gcal = + CalendarSystem.getGregorianCalendar(); + private static BaseCalendar jcal; + + private transient long fastTime; + + /* + * If cdate is null, then fastTime indicates the time in millis. + * If cdate.isNormalized() is true, then fastTime and cdate are in + * synch. Otherwise, fastTime is ignored, and cdate indicates the + * time. + */ + private transient BaseCalendar.Date cdate; + + // Initialized just before the value is used. See parse(). + private static int defaultCenturyStart; + + /* use serialVersionUID from modified java.util.Date for + * interoperability with JDK1.1. The Date was modified to write + * and read only the UTC time. + */ + private static final long serialVersionUID = 7523967970034938905L; + + /** + * Allocates a Date object and initializes it so that + * it represents the time at which it was allocated, measured to the + * nearest millisecond. + * + * @see java.lang.System#currentTimeMillis() + */ + public Date() { + this(System.currentTimeMillis()); + } + + /** + * Allocates a Date object and initializes it to + * represent the specified number of milliseconds since the + * standard base time known as "the epoch", namely January 1, + * 1970, 00:00:00 GMT. + * + * @param date the milliseconds since January 1, 1970, 00:00:00 GMT. + * @see java.lang.System#currentTimeMillis() + */ + public Date(long date) { + fastTime = date; + } + + /** + * Allocates a Date object and initializes it so that + * it represents midnight, local time, at the beginning of the day + * specified by the year, month, and + * date arguments. + * + * @param year the year minus 1900. + * @param month the month between 0-11. + * @param date the day of the month between 1-31. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(year + 1900, month, date) + * or GregorianCalendar(year + 1900, month, date). + */ + @Deprecated + public Date(int year, int month, int date) { + this(year, month, date, 0, 0, 0); + } + + /** + * Allocates a Date object and initializes it so that + * it represents the instant at the start of the minute specified by + * the year, month, date, + * hrs, and min arguments, in the local + * time zone. + * + * @param year the year minus 1900. + * @param month the month between 0-11. + * @param date the day of the month between 1-31. + * @param hrs the hours between 0-23. + * @param min the minutes between 0-59. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(year + 1900, month, date, + * hrs, min) or GregorianCalendar(year + 1900, + * month, date, hrs, min). + */ + @Deprecated + public Date(int year, int month, int date, int hrs, int min) { + this(year, month, date, hrs, min, 0); + } + + /** + * Allocates a Date object and initializes it so that + * it represents the instant at the start of the second specified + * by the year, month, date, + * hrs, min, and sec arguments, + * in the local time zone. + * + * @param year the year minus 1900. + * @param month the month between 0-11. + * @param date the day of the month between 1-31. + * @param hrs the hours between 0-23. + * @param min the minutes between 0-59. + * @param sec the seconds between 0-59. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(year + 1900, month, date, + * hrs, min, sec) or GregorianCalendar(year + 1900, + * month, date, hrs, min, sec). + */ + @Deprecated + public Date(int year, int month, int date, int hrs, int min, int sec) { + int y = year + 1900; + // month is 0-based. So we have to normalize month to support Long.MAX_VALUE. + if (month >= 12) { + y += month / 12; + month %= 12; + } else if (month < 0) { + y += CalendarUtils.floorDivide(month, 12); + month = CalendarUtils.mod(month, 12); + } + BaseCalendar cal = getCalendarSystem(y); + cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.getDefaultRef()); + cdate.setNormalizedDate(y, month + 1, date).setTimeOfDay(hrs, min, sec, 0); + getTimeImpl(); + cdate = null; + } + + /** + * Allocates a Date object and initializes it so that + * it represents the date and time indicated by the string + * s, which is interpreted as if by the + * {@link Date#parse} method. + * + * @param s a string representation of the date. + * @see java.text.DateFormat + * @see java.util.Date#parse(java.lang.String) + * @deprecated As of JDK version 1.1, + * replaced by DateFormat.parse(String s). + */ + @Deprecated + public Date(String s) { + this(parse(s)); + } + + /** + * Return a copy of this object. + */ + public Object clone() { + Date d = null; + try { + d = (Date)super.clone(); + if (cdate != null) { + d.cdate = (BaseCalendar.Date) cdate.clone(); + } + } catch (CloneNotSupportedException e) {} // Won't happen + return d; + } + + /** + * Determines the date and time based on the arguments. The + * arguments are interpreted as a year, month, day of the month, + * hour of the day, minute within the hour, and second within the + * minute, exactly as for the Date constructor with six + * arguments, except that the arguments are interpreted relative + * to UTC rather than to the local time zone. The time indicated is + * returned represented as the distance, measured in milliseconds, + * of that time from the epoch (00:00:00 GMT on January 1, 1970). + * + * @param year the year minus 1900. + * @param month the month between 0-11. + * @param date the day of the month between 1-31. + * @param hrs the hours between 0-23. + * @param min the minutes between 0-59. + * @param sec the seconds between 0-59. + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT for + * the date and time specified by the arguments. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(year + 1900, month, date, + * hrs, min, sec) or GregorianCalendar(year + 1900, + * month, date, hrs, min, sec), using a UTC + * TimeZone, followed by Calendar.getTime().getTime(). + */ + @Deprecated + public static long UTC(int year, int month, int date, + int hrs, int min, int sec) { + int y = year + 1900; + // month is 0-based. So we have to normalize month to support Long.MAX_VALUE. + if (month >= 12) { + y += month / 12; + month %= 12; + } else if (month < 0) { + y += CalendarUtils.floorDivide(month, 12); + month = CalendarUtils.mod(month, 12); + } + int m = month + 1; + BaseCalendar cal = getCalendarSystem(y); + BaseCalendar.Date udate = (BaseCalendar.Date) cal.newCalendarDate(null); + udate.setNormalizedDate(y, m, date).setTimeOfDay(hrs, min, sec, 0); + + // Use a Date instance to perform normalization. Its fastTime + // is the UTC value after the normalization. + Date d = new Date(0); + d.normalize(udate); + return d.fastTime; + } + + /** + * Attempts to interpret the string s as a representation + * of a date and time. If the attempt is successful, the time + * indicated is returned represented as the distance, measured in + * milliseconds, of that time from the epoch (00:00:00 GMT on + * January 1, 1970). If the attempt fails, an + * IllegalArgumentException is thrown. + *

+ * It accepts many syntaxes; in particular, it recognizes the IETF + * standard date syntax: "Sat, 12 Aug 1995 13:30:00 GMT". It also + * understands the continental U.S. time-zone abbreviations, but for + * general use, a time-zone offset should be used: "Sat, 12 Aug 1995 + * 13:30:00 GMT+0430" (4 hours, 30 minutes west of the Greenwich + * meridian). If no time zone is specified, the local time zone is + * assumed. GMT and UTC are considered equivalent. + *

+ * The string s is processed from left to right, looking for + * data of interest. Any material in s that is within the + * ASCII parenthesis characters ( and ) is ignored. + * Parentheses may be nested. Otherwise, the only characters permitted + * within s are these ASCII characters: + *

+     * abcdefghijklmnopqrstuvwxyz
+     * ABCDEFGHIJKLMNOPQRSTUVWXYZ
+     * 0123456789,+-:/
+ * and whitespace characters.

+ * A consecutive sequence of decimal digits is treated as a decimal + * number:

    + *
  • If a number is preceded by + or - and a year + * has already been recognized, then the number is a time-zone + * offset. If the number is less than 24, it is an offset measured + * in hours. Otherwise, it is regarded as an offset in minutes, + * expressed in 24-hour time format without punctuation. A + * preceding - means a westward offset. Time zone offsets + * are always relative to UTC (Greenwich). Thus, for example, + * -5 occurring in the string would mean "five hours west + * of Greenwich" and +0430 would mean "four hours and + * thirty minutes east of Greenwich." It is permitted for the + * string to specify GMT, UT, or UTC + * redundantly-for example, GMT-5 or utc+0430. + *
  • The number is regarded as a year number if one of the + * following conditions is true: + *
      + *
    • The number is equal to or greater than 70 and followed by a + * space, comma, slash, or end of string + *
    • The number is less than 70, and both a month and a day of + * the month have already been recognized
    • + *
    + * If the recognized year number is less than 100, it is + * interpreted as an abbreviated year relative to a century of + * which dates are within 80 years before and 19 years after + * the time when the Date class is initialized. + * After adjusting the year number, 1900 is subtracted from + * it. For example, if the current year is 1999 then years in + * the range 19 to 99 are assumed to mean 1919 to 1999, while + * years from 0 to 18 are assumed to mean 2000 to 2018. Note + * that this is slightly different from the interpretation of + * years less than 100 that is used in {@link java.text.SimpleDateFormat}. + *
  • If the number is followed by a colon, it is regarded as an hour, + * unless an hour has already been recognized, in which case it is + * regarded as a minute. + *
  • If the number is followed by a slash, it is regarded as a month + * (it is decreased by 1 to produce a number in the range 0 + * to 11), unless a month has already been recognized, in + * which case it is regarded as a day of the month. + *
  • If the number is followed by whitespace, a comma, a hyphen, or + * end of string, then if an hour has been recognized but not a + * minute, it is regarded as a minute; otherwise, if a minute has + * been recognized but not a second, it is regarded as a second; + * otherwise, it is regarded as a day of the month.

+ * A consecutive sequence of letters is regarded as a word and treated + * as follows:

    + *
  • A word that matches AM, ignoring case, is ignored (but + * the parse fails if an hour has not been recognized or is less + * than 1 or greater than 12). + *
  • A word that matches PM, ignoring case, adds 12 + * to the hour (but the parse fails if an hour has not been + * recognized or is less than 1 or greater than 12). + *
  • Any word that matches any prefix of SUNDAY, MONDAY, TUESDAY, + * WEDNESDAY, THURSDAY, FRIDAY, or SATURDAY, ignoring + * case, is ignored. For example, sat, Friday, TUE, and + * Thurs are ignored. + *
  • Otherwise, any word that matches any prefix of JANUARY, + * FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, + * OCTOBER, NOVEMBER, or DECEMBER, ignoring case, and + * considering them in the order given here, is recognized as + * specifying a month and is converted to a number (0 to + * 11). For example, aug, Sept, april, and + * NOV are recognized as months. So is Ma, which + * is recognized as MARCH, not MAY. + *
  • Any word that matches GMT, UT, or UTC, ignoring + * case, is treated as referring to UTC. + *
  • Any word that matches EST, CST, MST, or PST, + * ignoring case, is recognized as referring to the time zone in + * North America that is five, six, seven, or eight hours west of + * Greenwich, respectively. Any word that matches EDT, CDT, + * MDT, or PDT, ignoring case, is recognized as + * referring to the same time zone, respectively, during daylight + * saving time.

+ * Once the entire string s has been scanned, it is converted to a time + * result in one of two ways. If a time zone or time-zone offset has been + * recognized, then the year, month, day of month, hour, minute, and + * second are interpreted in UTC and then the time-zone offset is + * applied. Otherwise, the year, month, day of month, hour, minute, and + * second are interpreted in the local time zone. + * + * @param s a string to be parsed as a date. + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT + * represented by the string argument. + * @see java.text.DateFormat + * @deprecated As of JDK version 1.1, + * replaced by DateFormat.parse(String s). + */ + @Deprecated + public static long parse(String s) { + int year = Integer.MIN_VALUE; + int mon = -1; + int mday = -1; + int hour = -1; + int min = -1; + int sec = -1; + int millis = -1; + int c = -1; + int i = 0; + int n = -1; + int wst = -1; + int tzoffset = -1; + int prevc = 0; + syntax: + { + if (s == null) + break syntax; + int limit = s.length(); + while (i < limit) { + c = s.charAt(i); + i++; + if (c <= ' ' || c == ',') + continue; + if (c == '(') { // skip comments + int depth = 1; + while (i < limit) { + c = s.charAt(i); + i++; + if (c == '(') depth++; + else if (c == ')') + if (--depth <= 0) + break; + } + continue; + } + if ('0' <= c && c <= '9') { + n = c - '0'; + while (i < limit && '0' <= (c = s.charAt(i)) && c <= '9') { + n = n * 10 + c - '0'; + i++; + } + if (prevc == '+' || prevc == '-' && year != Integer.MIN_VALUE) { + // timezone offset + if (n < 24) + n = n * 60; // EG. "GMT-3" + else + n = n % 100 + n / 100 * 60; // eg "GMT-0430" + if (prevc == '+') // plus means east of GMT + n = -n; + if (tzoffset != 0 && tzoffset != -1) + break syntax; + tzoffset = n; + } else if (n >= 70) + if (year != Integer.MIN_VALUE) + break syntax; + else if (c <= ' ' || c == ',' || c == '/' || i >= limit) + // year = n < 1900 ? n : n - 1900; + year = n; + else + break syntax; + else if (c == ':') + if (hour < 0) + hour = (byte) n; + else if (min < 0) + min = (byte) n; + else + break syntax; + else if (c == '/') + if (mon < 0) + mon = (byte) (n - 1); + else if (mday < 0) + mday = (byte) n; + else + break syntax; + else if (i < limit && c != ',' && c > ' ' && c != '-') + break syntax; + else if (hour >= 0 && min < 0) + min = (byte) n; + else if (min >= 0 && sec < 0) + sec = (byte) n; + else if (mday < 0) + mday = (byte) n; + // Handle two-digit years < 70 (70-99 handled above). + else if (year == Integer.MIN_VALUE && mon >= 0 && mday >= 0) + year = n; + else + break syntax; + prevc = 0; + } else if (c == '/' || c == ':' || c == '+' || c == '-') + prevc = c; + else { + int st = i - 1; + while (i < limit) { + c = s.charAt(i); + if (!('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z')) + break; + i++; + } + if (i <= st + 1) + break syntax; + int k; + for (k = wtb.length; --k >= 0;) + if (wtb[k].regionMatches(true, 0, s, st, i - st)) { + int action = ttb[k]; + if (action != 0) { + if (action == 1) { // pm + if (hour > 12 || hour < 1) + break syntax; + else if (hour < 12) + hour += 12; + } else if (action == 14) { // am + if (hour > 12 || hour < 1) + break syntax; + else if (hour == 12) + hour = 0; + } else if (action <= 13) { // month! + if (mon < 0) + mon = (byte) (action - 2); + else + break syntax; + } else { + tzoffset = action - 10000; + } + } + break; + } + if (k < 0) + break syntax; + prevc = 0; + } + } + if (year == Integer.MIN_VALUE || mon < 0 || mday < 0) + break syntax; + // Parse 2-digit years within the correct default century. + if (year < 100) { + synchronized (Date.class) { + if (defaultCenturyStart == 0) { + defaultCenturyStart = gcal.getCalendarDate().getYear() - 80; + } + } + year += (defaultCenturyStart / 100) * 100; + if (year < defaultCenturyStart) year += 100; + } + if (sec < 0) + sec = 0; + if (min < 0) + min = 0; + if (hour < 0) + hour = 0; + BaseCalendar cal = getCalendarSystem(year); + if (tzoffset == -1) { // no time zone specified, have to use local + BaseCalendar.Date ldate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.getDefaultRef()); + ldate.setDate(year, mon + 1, mday); + ldate.setTimeOfDay(hour, min, sec, 0); + return cal.getTime(ldate); + } + BaseCalendar.Date udate = (BaseCalendar.Date) cal.newCalendarDate(null); // no time zone + udate.setDate(year, mon + 1, mday); + udate.setTimeOfDay(hour, min, sec, 0); + return cal.getTime(udate) + tzoffset * (60 * 1000); + } + // syntax error + throw new IllegalArgumentException(); + } + private final static String wtb[] = { + "am", "pm", + "monday", "tuesday", "wednesday", "thursday", "friday", + "saturday", "sunday", + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december", + "gmt", "ut", "utc", "est", "edt", "cst", "cdt", + "mst", "mdt", "pst", "pdt" + }; + private final static int ttb[] = { + 14, 1, 0, 0, 0, 0, 0, 0, 0, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 10000 + 0, 10000 + 0, 10000 + 0, // GMT/UT/UTC + 10000 + 5 * 60, 10000 + 4 * 60, // EST/EDT + 10000 + 6 * 60, 10000 + 5 * 60, // CST/CDT + 10000 + 7 * 60, 10000 + 6 * 60, // MST/MDT + 10000 + 8 * 60, 10000 + 7 * 60 // PST/PDT + }; + + /** + * Returns a value that is the result of subtracting 1900 from the + * year that contains or begins with the instant in time represented + * by this Date object, as interpreted in the local + * time zone. + * + * @return the year represented by this date, minus 1900. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.YEAR) - 1900. + */ + @Deprecated + public int getYear() { + return normalize().getYear() - 1900; + } + + /** + * Sets the year of this Date object to be the specified + * value plus 1900. This Date object is modified so + * that it represents a point in time within the specified year, + * with the month, date, hour, minute, and second the same as + * before, as interpreted in the local time zone. (Of course, if + * the date was February 29, for example, and the year is set to a + * non-leap year, then the new date will be treated as if it were + * on March 1.) + * + * @param year the year value. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.YEAR, year + 1900). + */ + @Deprecated + public void setYear(int year) { + getCalendarDate().setNormalizedYear(year + 1900); + } + + /** + * Returns a number representing the month that contains or begins + * with the instant in time represented by this Date object. + * The value returned is between 0 and 11, + * with the value 0 representing January. + * + * @return the month represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.MONTH). + */ + @Deprecated + public int getMonth() { + return normalize().getMonth() - 1; // adjust 1-based to 0-based + } + + /** + * Sets the month of this date to the specified value. This + * Date object is modified so that it represents a point + * in time within the specified month, with the year, date, hour, + * minute, and second the same as before, as interpreted in the + * local time zone. If the date was October 31, for example, and + * the month is set to June, then the new date will be treated as + * if it were on July 1, because June has only 30 days. + * + * @param month the month value between 0-11. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.MONTH, int month). + */ + @Deprecated + public void setMonth(int month) { + int y = 0; + if (month >= 12) { + y = month / 12; + month %= 12; + } else if (month < 0) { + y = CalendarUtils.floorDivide(month, 12); + month = CalendarUtils.mod(month, 12); + } + BaseCalendar.Date d = getCalendarDate(); + if (y != 0) { + d.setNormalizedYear(d.getNormalizedYear() + y); + } + d.setMonth(month + 1); // adjust 0-based to 1-based month numbering + } + + /** + * Returns the day of the month represented by this Date object. + * The value returned is between 1 and 31 + * representing the day of the month that contains or begins with the + * instant in time represented by this Date object, as + * interpreted in the local time zone. + * + * @return the day of the month represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.DAY_OF_MONTH). + * @deprecated + */ + @Deprecated + public int getDate() { + return normalize().getDayOfMonth(); + } + + /** + * Sets the day of the month of this Date object to the + * specified value. This Date object is modified so that + * it represents a point in time within the specified day of the + * month, with the year, month, hour, minute, and second the same + * as before, as interpreted in the local time zone. If the date + * was April 30, for example, and the date is set to 31, then it + * will be treated as if it were on May 1, because April has only + * 30 days. + * + * @param date the day of the month value between 1-31. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.DAY_OF_MONTH, int date). + */ + @Deprecated + public void setDate(int date) { + getCalendarDate().setDayOfMonth(date); + } + + /** + * Returns the day of the week represented by this date. The + * returned value (0 = Sunday, 1 = Monday, + * 2 = Tuesday, 3 = Wednesday, 4 = + * Thursday, 5 = Friday, 6 = Saturday) + * represents the day of the week that contains or begins with + * the instant in time represented by this Date object, + * as interpreted in the local time zone. + * + * @return the day of the week represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.DAY_OF_WEEK). + */ + @Deprecated + public int getDay() { + return normalize().getDayOfWeek() - gcal.SUNDAY; + } + + /** + * Returns the hour represented by this Date object. The + * returned value is a number (0 through 23) + * representing the hour within the day that contains or begins + * with the instant in time represented by this Date + * object, as interpreted in the local time zone. + * + * @return the hour represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.HOUR_OF_DAY). + */ + @Deprecated + public int getHours() { + return normalize().getHours(); + } + + /** + * Sets the hour of this Date object to the specified value. + * This Date object is modified so that it represents a point + * in time within the specified hour of the day, with the year, month, + * date, minute, and second the same as before, as interpreted in the + * local time zone. + * + * @param hours the hour value. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.HOUR_OF_DAY, int hours). + */ + @Deprecated + public void setHours(int hours) { + getCalendarDate().setHours(hours); + } + + /** + * Returns the number of minutes past the hour represented by this date, + * as interpreted in the local time zone. + * The value returned is between 0 and 59. + * + * @return the number of minutes past the hour represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.MINUTE). + */ + @Deprecated + public int getMinutes() { + return normalize().getMinutes(); + } + + /** + * Sets the minutes of this Date object to the specified value. + * This Date object is modified so that it represents a point + * in time within the specified minute of the hour, with the year, month, + * date, hour, and second the same as before, as interpreted in the + * local time zone. + * + * @param minutes the value of the minutes. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.MINUTE, int minutes). + */ + @Deprecated + public void setMinutes(int minutes) { + getCalendarDate().setMinutes(minutes); + } + + /** + * Returns the number of seconds past the minute represented by this date. + * The value returned is between 0 and 61. The + * values 60 and 61 can only occur on those + * Java Virtual Machines that take leap seconds into account. + * + * @return the number of seconds past the minute represented by this date. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.get(Calendar.SECOND). + */ + @Deprecated + public int getSeconds() { + return normalize().getSeconds(); + } + + /** + * Sets the seconds of this Date to the specified value. + * This Date object is modified so that it represents a + * point in time within the specified second of the minute, with + * the year, month, date, hour, and minute the same as before, as + * interpreted in the local time zone. + * + * @param seconds the seconds value. + * @see java.util.Calendar + * @deprecated As of JDK version 1.1, + * replaced by Calendar.set(Calendar.SECOND, int seconds). + */ + @Deprecated + public void setSeconds(int seconds) { + getCalendarDate().setSeconds(seconds); + } + + /** + * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT + * represented by this Date object. + * + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT + * represented by this date. + */ + public long getTime() { + return getTimeImpl(); + } + + private final long getTimeImpl() { + if (cdate != null && !cdate.isNormalized()) { + normalize(); + } + return fastTime; + } + + /** + * Sets this Date object to represent a point in time that is + * time milliseconds after January 1, 1970 00:00:00 GMT. + * + * @param time the number of milliseconds. + */ + public void setTime(long time) { + fastTime = time; + cdate = null; + } + + /** + * Tests if this date is before the specified date. + * + * @param when a date. + * @return true if and only if the instant of time + * represented by this Date object is strictly + * earlier than the instant represented by when; + * false otherwise. + * @exception NullPointerException if when is null. + */ + public boolean before(Date when) { + return getMillisOf(this) < getMillisOf(when); + } + + /** + * Tests if this date is after the specified date. + * + * @param when a date. + * @return true if and only if the instant represented + * by this Date object is strictly later than the + * instant represented by when; + * false otherwise. + * @exception NullPointerException if when is null. + */ + public boolean after(Date when) { + return getMillisOf(this) > getMillisOf(when); + } + + /** + * Compares two dates for equality. + * The result is true if and only if the argument is + * not null and is a Date object that + * represents the same point in time, to the millisecond, as this object. + *

+ * Thus, two Date objects are equal if and only if the + * getTime method returns the same long + * value for both. + * + * @param obj the object to compare with. + * @return true if the objects are the same; + * false otherwise. + * @see java.util.Date#getTime() + */ + public boolean equals(Object obj) { + return obj instanceof Date && getTime() == ((Date) obj).getTime(); + } + + /** + * Returns the millisecond value of this Date object + * without affecting its internal state. + */ + static final long getMillisOf(Date date) { + if (date.cdate == null || date.cdate.isNormalized()) { + return date.fastTime; + } + BaseCalendar.Date d = (BaseCalendar.Date) date.cdate.clone(); + return gcal.getTime(d); + } + + /** + * Compares two Dates for ordering. + * + * @param anotherDate the Date to be compared. + * @return the value 0 if the argument Date is equal to + * this Date; a value less than 0 if this Date + * is before the Date argument; and a value greater than + * 0 if this Date is after the Date argument. + * @since 1.2 + * @exception NullPointerException if anotherDate is null. + */ + public int compareTo(Date anotherDate) { + long thisTime = getMillisOf(this); + long anotherTime = getMillisOf(anotherDate); + return (thisTimelong + * value returned by the {@link Date#getTime} + * method. That is, the hash code is the value of the expression: + *

+     * (int)(this.getTime()^(this.getTime() >>> 32))
+ * + * @return a hash code value for this object. + */ + public int hashCode() { + long ht = this.getTime(); + return (int) ht ^ (int) (ht >> 32); + } + + /** + * Converts this Date object to a String + * of the form: + *
+     * dow mon dd hh:mm:ss zzz yyyy
+ * where:
    + *
  • dow is the day of the week (Sun, Mon, Tue, Wed, + * Thu, Fri, Sat). + *
  • mon is the month (Jan, Feb, Mar, Apr, May, Jun, + * Jul, Aug, Sep, Oct, Nov, Dec). + *
  • dd is the day of the month (01 through + * 31), as two decimal digits. + *
  • hh is the hour of the day (00 through + * 23), as two decimal digits. + *
  • mm is the minute within the hour (00 through + * 59), as two decimal digits. + *
  • ss is the second within the minute (00 through + * 61, as two decimal digits. + *
  • zzz is the time zone (and may reflect daylight saving + * time). Standard time zone abbreviations include those + * recognized by the method parse. If time zone + * information is not available, then zzz is empty - + * that is, it consists of no characters at all. + *
  • yyyy is the year, as four decimal digits. + *
+ * + * @return a string representation of this date. + * @see java.util.Date#toLocaleString() + * @see java.util.Date#toGMTString() + */ + public String toString() { + // "EEE MMM dd HH:mm:ss zzz yyyy"; + BaseCalendar.Date date = normalize(); + StringBuilder sb = new StringBuilder(28); + int index = date.getDayOfWeek(); + if (index == gcal.SUNDAY) { + index = 8; + } + convertToAbbr(sb, wtb[index]).append(' '); // EEE + convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM + CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 2).append(' '); // dd + + CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH + CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm + CalendarUtils.sprintf0d(sb, date.getSeconds(), 2).append(' '); // ss + TimeZone zi = date.getZone(); + if (zi != null) { + sb.append(zi.getDisplayName(date.isDaylightTime(), zi.SHORT, Locale.US)); // zzz + } else { + sb.append("GMT"); + } + sb.append(' ').append(date.getYear()); // yyyy + return sb.toString(); + } + + /** + * Converts the given name to its 3-letter abbreviation (e.g., + * "monday" -> "Mon") and stored the abbreviation in the given + * StringBuilder. + */ + private static final StringBuilder convertToAbbr(StringBuilder sb, String name) { + sb.append(Character.toUpperCase(name.charAt(0))); + sb.append(name.charAt(1)).append(name.charAt(2)); + return sb; + } + + /** + * Creates a string representation of this Date object in an + * implementation-dependent form. The intent is that the form should + * be familiar to the user of the Java application, wherever it may + * happen to be running. The intent is comparable to that of the + * "%c" format supported by the strftime() + * function of ISO C. + * + * @return a string representation of this date, using the locale + * conventions. + * @see java.text.DateFormat + * @see java.util.Date#toString() + * @see java.util.Date#toGMTString() + * @deprecated As of JDK version 1.1, + * replaced by DateFormat.format(Date date). + */ + @Deprecated + public String toLocaleString() { + DateFormat formatter = DateFormat.getDateTimeInstance(); + return formatter.format(this); + } + + /** + * Creates a string representation of this Date object of + * the form: + * + * d mon yyyy hh:mm:ss GMT + * where:
    + *
  • d is the day of the month (1 through 31), + * as one or two decimal digits. + *
  • mon is the month (Jan, Feb, Mar, Apr, May, Jun, Jul, + * Aug, Sep, Oct, Nov, Dec). + *
  • yyyy is the year, as four decimal digits. + *
  • hh is the hour of the day (00 through 23), + * as two decimal digits. + *
  • mm is the minute within the hour (00 through + * 59), as two decimal digits. + *
  • ss is the second within the minute (00 through + * 61), as two decimal digits. + *
  • GMT is exactly the ASCII letters "GMT" to indicate + * Greenwich Mean Time. + *

+ * The result does not depend on the local time zone. + * + * @return a string representation of this date, using the Internet GMT + * conventions. + * @see java.text.DateFormat + * @see java.util.Date#toString() + * @see java.util.Date#toLocaleString() + * @deprecated As of JDK version 1.1, + * replaced by DateFormat.format(Date date), using a + * GMT TimeZone. + */ + @Deprecated + public String toGMTString() { + // d MMM yyyy HH:mm:ss 'GMT' + long t = getTime(); + BaseCalendar cal = getCalendarSystem(t); + BaseCalendar.Date date = + (BaseCalendar.Date) cal.getCalendarDate(getTime(), (TimeZone)null); + StringBuilder sb = new StringBuilder(32); + CalendarUtils.sprintf0d(sb, date.getDayOfMonth(), 1).append(' '); // d + convertToAbbr(sb, wtb[date.getMonth() - 1 + 2 + 7]).append(' '); // MMM + sb.append(date.getYear()).append(' '); // yyyy + CalendarUtils.sprintf0d(sb, date.getHours(), 2).append(':'); // HH + CalendarUtils.sprintf0d(sb, date.getMinutes(), 2).append(':'); // mm + CalendarUtils.sprintf0d(sb, date.getSeconds(), 2); // ss + sb.append(" GMT"); // ' GMT' + return sb.toString(); + } + + /** + * Returns the offset, measured in minutes, for the local time zone + * relative to UTC that is appropriate for the time represented by + * this Date object. + *

+ * For example, in Massachusetts, five time zones west of Greenwich: + *

+     * new Date(96, 1, 14).getTimezoneOffset() returns 300
+ * because on February 14, 1996, standard time (Eastern Standard Time) + * is in use, which is offset five hours from UTC; but: + *
+     * new Date(96, 5, 1).getTimezoneOffset() returns 240
+ * because on June 1, 1996, daylight saving time (Eastern Daylight Time) + * is in use, which is offset only four hours from UTC.

+ * This method produces the same result as if it computed: + *

+     * (this.getTime() - UTC(this.getYear(),
+     *                       this.getMonth(),
+     *                       this.getDate(),
+     *                       this.getHours(),
+     *                       this.getMinutes(),
+     *                       this.getSeconds())) / (60 * 1000)
+     * 
+ * + * @return the time-zone offset, in minutes, for the current time zone. + * @see java.util.Calendar#ZONE_OFFSET + * @see java.util.Calendar#DST_OFFSET + * @see java.util.TimeZone#getDefault + * @deprecated As of JDK version 1.1, + * replaced by -(Calendar.get(Calendar.ZONE_OFFSET) + + * Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000). + */ + @Deprecated + public int getTimezoneOffset() { + int zoneOffset; + if (cdate == null) { + TimeZone tz = TimeZone.getDefaultRef(); + if (tz instanceof ZoneInfo) { + zoneOffset = ((ZoneInfo)tz).getOffsets(fastTime, null); + } else { + zoneOffset = tz.getOffset(fastTime); + } + } else { + normalize(); + zoneOffset = cdate.getZoneOffset(); + } + return -zoneOffset/60000; // convert to minutes + } + + private final BaseCalendar.Date getCalendarDate() { + if (cdate == null) { + BaseCalendar cal = getCalendarSystem(fastTime); + cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime, + TimeZone.getDefaultRef()); + } + return cdate; + } + + private final BaseCalendar.Date normalize() { + if (cdate == null) { + BaseCalendar cal = getCalendarSystem(fastTime); + cdate = (BaseCalendar.Date) cal.getCalendarDate(fastTime, + TimeZone.getDefaultRef()); + return cdate; + } + + // Normalize cdate with the TimeZone in cdate first. This is + // required for the compatible behavior. + if (!cdate.isNormalized()) { + cdate = normalize(cdate); + } + + // If the default TimeZone has changed, then recalculate the + // fields with the new TimeZone. + TimeZone tz = TimeZone.getDefaultRef(); + if (tz != cdate.getZone()) { + cdate.setZone(tz); + CalendarSystem cal = getCalendarSystem(cdate); + cal.getCalendarDate(fastTime, cdate); + } + return cdate; + } + + // fastTime and the returned data are in sync upon return. + private final BaseCalendar.Date normalize(BaseCalendar.Date date) { + int y = date.getNormalizedYear(); + int m = date.getMonth(); + int d = date.getDayOfMonth(); + int hh = date.getHours(); + int mm = date.getMinutes(); + int ss = date.getSeconds(); + int ms = date.getMillis(); + TimeZone tz = date.getZone(); + + // If the specified year can't be handled using a long value + // in milliseconds, GregorianCalendar is used for full + // compatibility with underflow and overflow. This is required + // by some JCK tests. The limits are based max year values - + // years that can be represented by max values of d, hh, mm, + // ss and ms. Also, let GregorianCalendar handle the default + // cutover year so that we don't need to worry about the + // transition here. + if (y == 1582 || y > 280000000 || y < -280000000) { + if (tz == null) { + tz = TimeZone.getTimeZone("GMT"); + } + GregorianCalendar gc = new GregorianCalendar(tz); + gc.clear(); + gc.set(gc.MILLISECOND, ms); + gc.set(y, m-1, d, hh, mm, ss); + fastTime = gc.getTimeInMillis(); + BaseCalendar cal = getCalendarSystem(fastTime); + date = (BaseCalendar.Date) cal.getCalendarDate(fastTime, tz); + return date; + } + + BaseCalendar cal = getCalendarSystem(y); + if (cal != getCalendarSystem(date)) { + date = (BaseCalendar.Date) cal.newCalendarDate(tz); + date.setNormalizedDate(y, m, d).setTimeOfDay(hh, mm, ss, ms); + } + // Perform the GregorianCalendar-style normalization. + fastTime = cal.getTime(date); + + // In case the normalized date requires the other calendar + // system, we need to recalculate it using the other one. + BaseCalendar ncal = getCalendarSystem(fastTime); + if (ncal != cal) { + date = (BaseCalendar.Date) ncal.newCalendarDate(tz); + date.setNormalizedDate(y, m, d).setTimeOfDay(hh, mm, ss, ms); + fastTime = ncal.getTime(date); + } + return date; + } + + /** + * Returns the Gregorian or Julian calendar system to use with the + * given date. Use Gregorian from October 15, 1582. + * + * @param year normalized calendar year (not -1900) + * @return the CalendarSystem to use for the specified date + */ + private static final BaseCalendar getCalendarSystem(int year) { + if (year >= 1582) { + return gcal; + } + return getJulianCalendar(); + } + + private static final BaseCalendar getCalendarSystem(long utc) { + // Quickly check if the time stamp given by `utc' is the Epoch + // or later. If it's before 1970, we convert the cutover to + // local time to compare. + if (utc >= 0 + || utc >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER + - TimeZone.getDefaultRef().getOffset(utc)) { + return gcal; + } + return getJulianCalendar(); + } + + private static final BaseCalendar getCalendarSystem(BaseCalendar.Date cdate) { + if (jcal == null) { + return gcal; + } + if (cdate.getEra() != null) { + return jcal; + } + return gcal; + } + + synchronized private static final BaseCalendar getJulianCalendar() { + if (jcal == null) { + jcal = (BaseCalendar) CalendarSystem.forName("julian"); + } + return jcal; + } + + /** + * Save the state of this object to a stream (i.e., serialize it). + * + * @serialData The value returned by getTime() + * is emitted (long). This represents the offset from + * January 1, 1970, 00:00:00 GMT in milliseconds. + */ + private void writeObject(ObjectOutputStream s) + throws IOException + { + s.writeLong(getTimeImpl()); + } + + /** + * Reconstitute this object from a stream (i.e., deserialize it). + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException + { + fastTime = s.readLong(); + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/Locale.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Locale.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,2560 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.io.Serializable; +import java.security.AccessController; +import java.text.MessageFormat; +import java.util.spi.LocaleNameProvider; + +import sun.security.action.GetPropertyAction; +import sun.util.LocaleServiceProviderPool; +import sun.util.locale.BaseLocale; +import sun.util.locale.InternalLocaleBuilder; +import sun.util.locale.LanguageTag; +import sun.util.locale.LocaleExtensions; +import sun.util.locale.LocaleObjectCache; +import sun.util.locale.LocaleSyntaxException; +import sun.util.locale.LocaleUtils; +import sun.util.locale.ParseStatus; +import sun.util.locale.UnicodeLocaleExtension; +import sun.util.resources.LocaleData; +import sun.util.resources.OpenListResourceBundle; + +/** + * A Locale object represents a specific geographical, political, + * or cultural region. An operation that requires a Locale to perform + * its task is called locale-sensitive and uses the Locale + * to tailor information for the user. For example, displaying a number + * is a locale-sensitive operation— the number should be formatted + * according to the customs and conventions of the user's native country, + * region, or culture. + * + *

The Locale class implements identifiers + * interchangeable with BCP 47 (IETF BCP 47, "Tags for Identifying + * Languages"), with support for the LDML (UTS#35, "Unicode Locale + * Data Markup Language") BCP 47-compatible extensions for locale data + * exchange. + * + *

A Locale object logically consists of the fields + * described below. + * + *

+ *
language
+ * + *
ISO 639 alpha-2 or alpha-3 language code, or registered + * language subtags up to 8 alpha letters (for future enhancements). + * When a language has both an alpha-2 code and an alpha-3 code, the + * alpha-2 code must be used. You can find a full list of valid + * language codes in the IANA Language Subtag Registry (search for + * "Type: language"). The language field is case insensitive, but + * Locale always canonicalizes to lower case.

+ * + *
Well-formed language values have the form + * [a-zA-Z]{2,8}. Note that this is not the the full + * BCP47 language production, since it excludes extlang. They are + * not needed since modern three-letter language codes replace + * them.

+ * + *
Example: "en" (English), "ja" (Japanese), "kok" (Konkani)

+ * + *
script
+ * + *
ISO 15924 alpha-4 script code. You can find a full list of + * valid script codes in the IANA Language Subtag Registry (search + * for "Type: script"). The script field is case insensitive, but + * Locale always canonicalizes to title case (the first + * letter is upper case and the rest of the letters are lower + * case).

+ * + *
Well-formed script values have the form + * [a-zA-Z]{4}

+ * + *
Example: "Latn" (Latin), "Cyrl" (Cyrillic)

+ * + *
country (region)
+ * + *
ISO 3166 alpha-2 country code or UN M.49 numeric-3 area code. + * You can find a full list of valid country and region codes in the + * IANA Language Subtag Registry (search for "Type: region"). The + * country (region) field is case insensitive, but + * Locale always canonicalizes to upper case.

+ * + *
Well-formed country/region values have + * the form [a-zA-Z]{2} | [0-9]{3}

+ * + *
Example: "US" (United States), "FR" (France), "029" + * (Caribbean)

+ * + *
variant
+ * + *
Any arbitrary value used to indicate a variation of a + * Locale. Where there are two or more variant values + * each indicating its own semantics, these values should be ordered + * by importance, with most important first, separated by + * underscore('_'). The variant field is case sensitive.

+ * + *
Note: IETF BCP 47 places syntactic restrictions on variant + * subtags. Also BCP 47 subtags are strictly used to indicate + * additional variations that define a language or its dialects that + * are not covered by any combinations of language, script and + * region subtags. You can find a full list of valid variant codes + * in the IANA Language Subtag Registry (search for "Type: variant"). + * + *

However, the variant field in Locale has + * historically been used for any kind of variation, not just + * language variations. For example, some supported variants + * available in Java SE Runtime Environments indicate alternative + * cultural behaviors such as calendar type or number script. In + * BCP 47 this kind of information, which does not identify the + * language, is supported by extension subtags or private use + * subtags.


+ * + *
Well-formed variant values have the form SUBTAG + * (('_'|'-') SUBTAG)* where SUBTAG = + * [0-9][0-9a-zA-Z]{3} | [0-9a-zA-Z]{5,8}. (Note: BCP 47 only + * uses hyphen ('-') as a delimiter, this is more lenient).

+ * + *
Example: "polyton" (Polytonic Greek), "POSIX"

+ * + *
extensions
+ * + *
A map from single character keys to string values, indicating + * extensions apart from language identification. The extensions in + * Locale implement the semantics and syntax of BCP 47 + * extension subtags and private use subtags. The extensions are + * case insensitive, but Locale canonicalizes all + * extension keys and values to lower case. Note that extensions + * cannot have empty values.

+ * + *
Well-formed keys are single characters from the set + * [0-9a-zA-Z]. Well-formed values have the form + * SUBTAG ('-' SUBTAG)* where for the key 'x' + * SUBTAG = [0-9a-zA-Z]{1,8} and for other keys + * SUBTAG = [0-9a-zA-Z]{2,8} (that is, 'x' allows + * single-character subtags).

+ * + *
Example: key="u"/value="ca-japanese" (Japanese Calendar), + * key="x"/value="java-1-7"
+ *
+ * + * Note: Although BCP 47 requires field values to be registered + * in the IANA Language Subtag Registry, the Locale class + * does not provide any validation features. The Builder + * only checks if an individual field satisfies the syntactic + * requirement (is well-formed), but does not validate the value + * itself. See {@link Builder} for details. + * + *

Unicode locale/language extension

+ * + *

UTS#35, "Unicode Locale Data Markup Language" defines optional + * attributes and keywords to override or refine the default behavior + * associated with a locale. A keyword is represented by a pair of + * key and type. For example, "nu-thai" indicates that Thai local + * digits (value:"thai") should be used for formatting numbers + * (key:"nu"). + * + *

The keywords are mapped to a BCP 47 extension value using the + * extension key 'u' ({@link #UNICODE_LOCALE_EXTENSION}). The above + * example, "nu-thai", becomes the extension "u-nu-thai".code + * + *

Thus, when a Locale object contains Unicode locale + * attributes and keywords, + * getExtension(UNICODE_LOCALE_EXTENSION) will return a + * String representing this information, for example, "nu-thai". The + * Locale class also provides {@link + * #getUnicodeLocaleAttributes}, {@link #getUnicodeLocaleKeys}, and + * {@link #getUnicodeLocaleType} which allow you to access Unicode + * locale attributes and key/type pairs directly. When represented as + * a string, the Unicode Locale Extension lists attributes + * alphabetically, followed by key/type sequences with keys listed + * alphabetically (the order of subtags comprising a key's type is + * fixed when the type is defined) + * + *

A well-formed locale key has the form + * [0-9a-zA-Z]{2}. A well-formed locale type has the + * form "" | [0-9a-zA-Z]{3,8} ('-' [0-9a-zA-Z]{3,8})* (it + * can be empty, or a series of subtags 3-8 alphanums in length). A + * well-formed locale attribute has the form + * [0-9a-zA-Z]{3,8} (it is a single subtag with the same + * form as a locale type subtag). + * + *

The Unicode locale extension specifies optional behavior in + * locale-sensitive services. Although the LDML specification defines + * various keys and values, actual locale-sensitive service + * implementations in a Java Runtime Environment might not support any + * particular Unicode locale attributes or key/type pairs. + * + *

Creating a Locale

+ * + *

There are several different ways to create a Locale + * object. + * + *

Builder
+ * + *

Using {@link Builder} you can construct a Locale object + * that conforms to BCP 47 syntax. + * + *

Constructors
+ * + *

The Locale class provides three constructors: + *

+ *
+ *     {@link #Locale(String language)}
+ *     {@link #Locale(String language, String country)}
+ *     {@link #Locale(String language, String country, String variant)}
+ * 
+ *
+ * These constructors allow you to create a Locale object + * with language, country and variant, but you cannot specify + * script or extensions. + * + *
Factory Methods
+ * + *

The method {@link #forLanguageTag} creates a Locale + * object for a well-formed BCP 47 language tag. + * + *

Locale Constants
+ * + *

The Locale class provides a number of convenient constants + * that you can use to create Locale objects for commonly used + * locales. For example, the following creates a Locale object + * for the United States: + *

+ *
+ *     Locale.US
+ * 
+ *
+ * + *

Use of Locale

+ * + *

Once you've created a Locale you can query it for information + * about itself. Use getCountry to get the country (or region) + * code and getLanguage to get the language code. + * You can use getDisplayCountry to get the + * name of the country suitable for displaying to the user. Similarly, + * you can use getDisplayLanguage to get the name of + * the language suitable for displaying to the user. Interestingly, + * the getDisplayXXX methods are themselves locale-sensitive + * and have two versions: one that uses the default locale and one + * that uses the locale specified as an argument. + * + *

The Java Platform provides a number of classes that perform locale-sensitive + * operations. For example, the NumberFormat class formats + * numbers, currency, and percentages in a locale-sensitive manner. Classes + * such as NumberFormat have several convenience methods + * for creating a default object of that type. For example, the + * NumberFormat class provides these three convenience methods + * for creating a default NumberFormat object: + *

+ *
+ *     NumberFormat.getInstance()
+ *     NumberFormat.getCurrencyInstance()
+ *     NumberFormat.getPercentInstance()
+ * 
+ *
+ * Each of these methods has two variants; one with an explicit locale + * and one without; the latter uses the default locale: + *
+ *
+ *     NumberFormat.getInstance(myLocale)
+ *     NumberFormat.getCurrencyInstance(myLocale)
+ *     NumberFormat.getPercentInstance(myLocale)
+ * 
+ *
+ * A Locale is the mechanism for identifying the kind of object + * (NumberFormat) that you would like to get. The locale is + * just a mechanism for identifying objects, + * not a container for the objects themselves. + * + *

Compatibility

+ * + *

In order to maintain compatibility with existing usage, Locale's + * constructors retain their behavior prior to the Java Runtime + * Environment version 1.7. The same is largely true for the + * toString method. Thus Locale objects can continue to + * be used as they were. In particular, clients who parse the output + * of toString into language, country, and variant fields can continue + * to do so (although this is strongly discouraged), although the + * variant field will have additional information in it if script or + * extensions are present. + * + *

In addition, BCP 47 imposes syntax restrictions that are not + * imposed by Locale's constructors. This means that conversions + * between some Locales and BCP 47 language tags cannot be made without + * losing information. Thus toLanguageTag cannot + * represent the state of locales whose language, country, or variant + * do not conform to BCP 47. + * + *

Because of these issues, it is recommended that clients migrate + * away from constructing non-conforming locales and use the + * forLanguageTag and Locale.Builder APIs instead. + * Clients desiring a string representation of the complete locale can + * then always rely on toLanguageTag for this purpose. + * + *

Special cases
+ * + *

For compatibility reasons, two + * non-conforming locales are treated as special cases. These are + * ja_JP_JP and th_TH_TH. These are ill-formed + * in BCP 47 since the variants are too short. To ease migration to BCP 47, + * these are treated specially during construction. These two cases (and only + * these) cause a constructor to generate an extension, all other values behave + * exactly as they did prior to Java 7. + * + *

Java has used ja_JP_JP to represent Japanese as used in + * Japan together with the Japanese Imperial calendar. This is now + * representable using a Unicode locale extension, by specifying the + * Unicode locale key ca (for "calendar") and type + * japanese. When the Locale constructor is called with the + * arguments "ja", "JP", "JP", the extension "u-ca-japanese" is + * automatically added. + * + *

Java has used th_TH_TH to represent Thai as used in + * Thailand together with Thai digits. This is also now representable using + * a Unicode locale extension, by specifying the Unicode locale key + * nu (for "number") and value thai. When the Locale + * constructor is called with the arguments "th", "TH", "TH", the + * extension "u-nu-thai" is automatically added. + * + *

Serialization
+ * + *

During serialization, writeObject writes all fields to the output + * stream, including extensions. + * + *

During deserialization, readResolve adds extensions as described + * in Special Cases, only + * for the two cases th_TH_TH and ja_JP_JP. + * + *

Legacy language codes
+ * + *

Locale's constructor has always converted three language codes to + * their earlier, obsoleted forms: he maps to iw, + * yi maps to ji, and id maps to + * in. This continues to be the case, in order to not break + * backwards compatibility. + * + *

The APIs added in 1.7 map between the old and new language codes, + * maintaining the old codes internal to Locale (so that + * getLanguage and toString reflect the old + * code), but using the new codes in the BCP 47 language tag APIs (so + * that toLanguageTag reflects the new one). This + * preserves the equivalence between Locales no matter which code or + * API is used to construct them. Java's default resource bundle + * lookup mechanism also implements this mapping, so that resources + * can be named using either convention, see {@link ResourceBundle.Control}. + * + *

Three-letter language/country(region) codes
+ * + *

The Locale constructors have always specified that the language + * and the country param be two characters in length, although in + * practice they have accepted any length. The specification has now + * been relaxed to allow language codes of two to eight characters and + * country (region) codes of two to three characters, and in + * particular, three-letter language codes and three-digit region + * codes as specified in the IANA Language Subtag Registry. For + * compatibility, the implementation still does not impose a length + * constraint. + * + * @see Builder + * @see ResourceBundle + * @see java.text.Format + * @see java.text.NumberFormat + * @see java.text.Collator + * @author Mark Davis + * @since 1.1 + */ +public final class Locale implements Cloneable, Serializable { + + static private final Cache LOCALECACHE = new Cache(); + + /** Useful constant for language. + */ + static public final Locale ENGLISH = createConstant("en", ""); + + /** Useful constant for language. + */ + static public final Locale FRENCH = createConstant("fr", ""); + + /** Useful constant for language. + */ + static public final Locale GERMAN = createConstant("de", ""); + + /** Useful constant for language. + */ + static public final Locale ITALIAN = createConstant("it", ""); + + /** Useful constant for language. + */ + static public final Locale JAPANESE = createConstant("ja", ""); + + /** Useful constant for language. + */ + static public final Locale KOREAN = createConstant("ko", ""); + + /** Useful constant for language. + */ + static public final Locale CHINESE = createConstant("zh", ""); + + /** Useful constant for language. + */ + static public final Locale SIMPLIFIED_CHINESE = createConstant("zh", "CN"); + + /** Useful constant for language. + */ + static public final Locale TRADITIONAL_CHINESE = createConstant("zh", "TW"); + + /** Useful constant for country. + */ + static public final Locale FRANCE = createConstant("fr", "FR"); + + /** Useful constant for country. + */ + static public final Locale GERMANY = createConstant("de", "DE"); + + /** Useful constant for country. + */ + static public final Locale ITALY = createConstant("it", "IT"); + + /** Useful constant for country. + */ + static public final Locale JAPAN = createConstant("ja", "JP"); + + /** Useful constant for country. + */ + static public final Locale KOREA = createConstant("ko", "KR"); + + /** Useful constant for country. + */ + static public final Locale CHINA = SIMPLIFIED_CHINESE; + + /** Useful constant for country. + */ + static public final Locale PRC = SIMPLIFIED_CHINESE; + + /** Useful constant for country. + */ + static public final Locale TAIWAN = TRADITIONAL_CHINESE; + + /** Useful constant for country. + */ + static public final Locale UK = createConstant("en", "GB"); + + /** Useful constant for country. + */ + static public final Locale US = createConstant("en", "US"); + + /** Useful constant for country. + */ + static public final Locale CANADA = createConstant("en", "CA"); + + /** Useful constant for country. + */ + static public final Locale CANADA_FRENCH = createConstant("fr", "CA"); + + /** + * Useful constant for the root locale. The root locale is the locale whose + * language, country, and variant are empty ("") strings. This is regarded + * as the base locale of all locales, and is used as the language/country + * neutral locale for the locale sensitive operations. + * + * @since 1.6 + */ + static public final Locale ROOT = createConstant("", ""); + + /** + * The key for the private use extension ('x'). + * + * @see #getExtension(char) + * @see Builder#setExtension(char, String) + * @since 1.7 + */ + static public final char PRIVATE_USE_EXTENSION = 'x'; + + /** + * The key for Unicode locale extension ('u'). + * + * @see #getExtension(char) + * @see Builder#setExtension(char, String) + * @since 1.7 + */ + static public final char UNICODE_LOCALE_EXTENSION = 'u'; + + /** serialization ID + */ + static final long serialVersionUID = 9149081749638150636L; + + /** + * Display types for retrieving localized names from the name providers. + */ + private static final int DISPLAY_LANGUAGE = 0; + private static final int DISPLAY_COUNTRY = 1; + private static final int DISPLAY_VARIANT = 2; + private static final int DISPLAY_SCRIPT = 3; + + /** + * Private constructor used by getInstance method + */ + private Locale(BaseLocale baseLocale, LocaleExtensions extensions) { + this.baseLocale = baseLocale; + this.localeExtensions = extensions; + } + + /** + * Construct a locale from language, country and variant. + * This constructor normalizes the language value to lowercase and + * the country value to uppercase. + *

+ * Note: + *

    + *
  • ISO 639 is not a stable standard; some of the language codes it defines + * (specifically "iw", "ji", and "in") have changed. This constructor accepts both the + * old codes ("iw", "ji", and "in") and the new codes ("he", "yi", and "id"), but all other + * API on Locale will return only the OLD codes. + *
  • For backward compatibility reasons, this constructor does not make + * any syntactic checks on the input. + *
  • The two cases ("ja", "JP", "JP") and ("th", "TH", "TH") are handled specially, + * see Special Cases for more information. + *
+ * + * @param language An ISO 639 alpha-2 or alpha-3 language code, or a language subtag + * up to 8 characters in length. See the Locale class description about + * valid language values. + * @param country An ISO 3166 alpha-2 country code or a UN M.49 numeric-3 area code. + * See the Locale class description about valid country values. + * @param variant Any arbitrary value used to indicate a variation of a Locale. + * See the Locale class description for the details. + * @exception NullPointerException thrown if any argument is null. + */ + public Locale(String language, String country, String variant) { + if (language== null || country == null || variant == null) { + throw new NullPointerException(); + } + baseLocale = BaseLocale.getInstance(convertOldISOCodes(language), "", country, variant); + localeExtensions = getCompatibilityExtensions(language, "", country, variant); + } + + /** + * Construct a locale from language and country. + * This constructor normalizes the language value to lowercase and + * the country value to uppercase. + *

+ * Note: + *

    + *
  • ISO 639 is not a stable standard; some of the language codes it defines + * (specifically "iw", "ji", and "in") have changed. This constructor accepts both the + * old codes ("iw", "ji", and "in") and the new codes ("he", "yi", and "id"), but all other + * API on Locale will return only the OLD codes. + *
  • For backward compatibility reasons, this constructor does not make + * any syntactic checks on the input. + *
+ * + * @param language An ISO 639 alpha-2 or alpha-3 language code, or a language subtag + * up to 8 characters in length. See the Locale class description about + * valid language values. + * @param country An ISO 3166 alpha-2 country code or a UN M.49 numeric-3 area code. + * See the Locale class description about valid country values. + * @exception NullPointerException thrown if either argument is null. + */ + public Locale(String language, String country) { + this(language, country, ""); + } + + /** + * Construct a locale from a language code. + * This constructor normalizes the language value to lowercase. + *

+ * Note: + *

    + *
  • ISO 639 is not a stable standard; some of the language codes it defines + * (specifically "iw", "ji", and "in") have changed. This constructor accepts both the + * old codes ("iw", "ji", and "in") and the new codes ("he", "yi", and "id"), but all other + * API on Locale will return only the OLD codes. + *
  • For backward compatibility reasons, this constructor does not make + * any syntactic checks on the input. + *
+ * + * @param language An ISO 639 alpha-2 or alpha-3 language code, or a language subtag + * up to 8 characters in length. See the Locale class description about + * valid language values. + * @exception NullPointerException thrown if argument is null. + * @since 1.4 + */ + public Locale(String language) { + this(language, "", ""); + } + + /** + * This method must be called only for creating the Locale.* + * constants due to making shortcuts. + */ + private static Locale createConstant(String lang, String country) { + BaseLocale base = BaseLocale.createInstance(lang, country); + return getInstance(base, null); + } + + /** + * Returns a Locale constructed from the given + * language, country and + * variant. If the same Locale instance + * is available in the cache, then that instance is + * returned. Otherwise, a new Locale instance is + * created and cached. + * + * @param language lowercase 2 to 8 language code. + * @param country uppercase two-letter ISO-3166 code and numric-3 UN M.49 area code. + * @param variant vendor and browser specific code. See class description. + * @return the Locale instance requested + * @exception NullPointerException if any argument is null. + */ + static Locale getInstance(String language, String country, String variant) { + return getInstance(language, "", country, variant, null); + } + + static Locale getInstance(String language, String script, String country, + String variant, LocaleExtensions extensions) { + if (language== null || script == null || country == null || variant == null) { + throw new NullPointerException(); + } + + if (extensions == null) { + extensions = getCompatibilityExtensions(language, script, country, variant); + } + + BaseLocale baseloc = BaseLocale.getInstance(language, script, country, variant); + return getInstance(baseloc, extensions); + } + + static Locale getInstance(BaseLocale baseloc, LocaleExtensions extensions) { + LocaleKey key = new LocaleKey(baseloc, extensions); + return LOCALECACHE.get(key); + } + + private static class Cache extends LocaleObjectCache { + private Cache() { + } + + @Override + protected Locale createObject(LocaleKey key) { + return new Locale(key.base, key.exts); + } + } + + private static final class LocaleKey { + private final BaseLocale base; + private final LocaleExtensions exts; + private final int hash; + + private LocaleKey(BaseLocale baseLocale, LocaleExtensions extensions) { + base = baseLocale; + exts = extensions; + + // Calculate the hash value here because it's always used. + int h = base.hashCode(); + if (exts != null) { + h ^= exts.hashCode(); + } + hash = h; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LocaleKey)) { + return false; + } + LocaleKey other = (LocaleKey)obj; + if (hash != other.hash || !base.equals(other.base)) { + return false; + } + if (exts == null) { + return other.exts == null; + } + return exts.equals(other.exts); + } + + @Override + public int hashCode() { + return hash; + } + } + + /** + * Gets the current value of the default locale for this instance + * of the Java Virtual Machine. + *

+ * The Java Virtual Machine sets the default locale during startup + * based on the host environment. It is used by many locale-sensitive + * methods if no locale is explicitly specified. + * It can be changed using the + * {@link #setDefault(java.util.Locale) setDefault} method. + * + * @return the default locale for this instance of the Java Virtual Machine + */ + public static Locale getDefault() { + // do not synchronize this method - see 4071298 + // it's OK if more than one default locale happens to be created + if (defaultLocale == null) { + initDefault(); + } + return defaultLocale; + } + + /** + * Gets the current value of the default locale for the specified Category + * for this instance of the Java Virtual Machine. + *

+ * The Java Virtual Machine sets the default locale during startup based + * on the host environment. It is used by many locale-sensitive methods + * if no locale is explicitly specified. It can be changed using the + * setDefault(Locale.Category, Locale) method. + * + * @param category - the specified category to get the default locale + * @throws NullPointerException - if category is null + * @return the default locale for the specified Category for this instance + * of the Java Virtual Machine + * @see #setDefault(Locale.Category, Locale) + * @since 1.7 + */ + public static Locale getDefault(Locale.Category category) { + // do not synchronize this method - see 4071298 + // it's OK if more than one default locale happens to be created + switch (category) { + case DISPLAY: + if (defaultDisplayLocale == null) { + initDefault(category); + } + return defaultDisplayLocale; + case FORMAT: + if (defaultFormatLocale == null) { + initDefault(category); + } + return defaultFormatLocale; + default: + assert false: "Unknown Category"; + } + return getDefault(); + } + + private static void initDefault() { + String language, region, script, country, variant; + language = AccessController.doPrivileged( + new GetPropertyAction("user.language", "en")); + // for compatibility, check for old user.region property + region = AccessController.doPrivileged( + new GetPropertyAction("user.region")); + if (region != null) { + // region can be of form country, country_variant, or _variant + int i = region.indexOf('_'); + if (i >= 0) { + country = region.substring(0, i); + variant = region.substring(i + 1); + } else { + country = region; + variant = ""; + } + script = ""; + } else { + script = AccessController.doPrivileged( + new GetPropertyAction("user.script", "")); + country = AccessController.doPrivileged( + new GetPropertyAction("user.country", "")); + variant = AccessController.doPrivileged( + new GetPropertyAction("user.variant", "")); + } + defaultLocale = getInstance(language, script, country, variant, null); + } + + private static void initDefault(Locale.Category category) { + // make sure defaultLocale is initialized + if (defaultLocale == null) { + initDefault(); + } + + Locale defaultCategoryLocale = getInstance( + AccessController.doPrivileged( + new GetPropertyAction(category.languageKey, defaultLocale.getLanguage())), + AccessController.doPrivileged( + new GetPropertyAction(category.scriptKey, defaultLocale.getScript())), + AccessController.doPrivileged( + new GetPropertyAction(category.countryKey, defaultLocale.getCountry())), + AccessController.doPrivileged( + new GetPropertyAction(category.variantKey, defaultLocale.getVariant())), + null); + + switch (category) { + case DISPLAY: + defaultDisplayLocale = defaultCategoryLocale; + break; + case FORMAT: + defaultFormatLocale = defaultCategoryLocale; + break; + } + } + + /** + * Sets the default locale for this instance of the Java Virtual Machine. + * This does not affect the host locale. + *

+ * If there is a security manager, its checkPermission + * method is called with a PropertyPermission("user.language", "write") + * permission before the default locale is changed. + *

+ * The Java Virtual Machine sets the default locale during startup + * based on the host environment. It is used by many locale-sensitive + * methods if no locale is explicitly specified. + *

+ * Since changing the default locale may affect many different areas + * of functionality, this method should only be used if the caller + * is prepared to reinitialize locale-sensitive code running + * within the same Java Virtual Machine. + *

+ * By setting the default locale with this method, all of the default + * locales for each Category are also set to the specified default locale. + * + * @throws SecurityException + * if a security manager exists and its + * checkPermission method doesn't allow the operation. + * @throws NullPointerException if newLocale is null + * @param newLocale the new default locale + * @see SecurityManager#checkPermission + * @see java.util.PropertyPermission + */ + public static synchronized void setDefault(Locale newLocale) { + setDefault(Category.DISPLAY, newLocale); + setDefault(Category.FORMAT, newLocale); + defaultLocale = newLocale; + } + + /** + * Sets the default locale for the specified Category for this instance + * of the Java Virtual Machine. This does not affect the host locale. + *

+ * If there is a security manager, its checkPermission method is called + * with a PropertyPermission("user.language", "write") permission before + * the default locale is changed. + *

+ * The Java Virtual Machine sets the default locale during startup based + * on the host environment. It is used by many locale-sensitive methods + * if no locale is explicitly specified. + *

+ * Since changing the default locale may affect many different areas of + * functionality, this method should only be used if the caller is + * prepared to reinitialize locale-sensitive code running within the + * same Java Virtual Machine. + *

+ * + * @param category - the specified category to set the default locale + * @param newLocale - the new default locale + * @throws SecurityException - if a security manager exists and its + * checkPermission method doesn't allow the operation. + * @throws NullPointerException - if category and/or newLocale is null + * @see SecurityManager#checkPermission(java.security.Permission) + * @see PropertyPermission + * @see #getDefault(Locale.Category) + * @since 1.7 + */ + public static synchronized void setDefault(Locale.Category category, + Locale newLocale) { + if (category == null) + throw new NullPointerException("Category cannot be NULL"); + if (newLocale == null) + throw new NullPointerException("Can't set default locale to NULL"); + + SecurityManager sm = System.getSecurityManager(); + if (sm != null) sm.checkPermission(new PropertyPermission + ("user.language", "write")); + switch (category) { + case DISPLAY: + defaultDisplayLocale = newLocale; + break; + case FORMAT: + defaultFormatLocale = newLocale; + break; + default: + assert false: "Unknown Category"; + } + } + + /** + * Returns an array of all installed locales. + * The returned array represents the union of locales supported + * by the Java runtime environment and by installed + * {@link java.util.spi.LocaleServiceProvider LocaleServiceProvider} + * implementations. It must contain at least a Locale + * instance equal to {@link java.util.Locale#US Locale.US}. + * + * @return An array of installed locales. + */ + public static Locale[] getAvailableLocales() { + return LocaleServiceProviderPool.getAllAvailableLocales(); + } + + /** + * Returns a list of all 2-letter country codes defined in ISO 3166. + * Can be used to create Locales. + *

+ * Note: The Locale class also supports other codes for + * country (region), such as 3-letter numeric UN M.49 area codes. + * Therefore, the list returned by this method does not contain ALL valid + * codes that can be used to create Locales. + */ + public static String[] getISOCountries() { + if (isoCountries == null) { + isoCountries = getISO2Table(LocaleISOData.isoCountryTable); + } + String[] result = new String[isoCountries.length]; + System.arraycopy(isoCountries, 0, result, 0, isoCountries.length); + return result; + } + + /** + * Returns a list of all 2-letter language codes defined in ISO 639. + * Can be used to create Locales. + *

+ * Note: + *

    + *
  • ISO 639 is not a stable standard— some languages' codes have changed. + * The list this function returns includes both the new and the old codes for the + * languages whose codes have changed. + *
  • The Locale class also supports language codes up to + * 8 characters in length. Therefore, the list returned by this method does + * not contain ALL valid codes that can be used to create Locales. + *
+ */ + public static String[] getISOLanguages() { + if (isoLanguages == null) { + isoLanguages = getISO2Table(LocaleISOData.isoLanguageTable); + } + String[] result = new String[isoLanguages.length]; + System.arraycopy(isoLanguages, 0, result, 0, isoLanguages.length); + return result; + } + + private static final String[] getISO2Table(String table) { + int len = table.length() / 5; + String[] isoTable = new String[len]; + for (int i = 0, j = 0; i < len; i++, j += 5) { + isoTable[i] = table.substring(j, j + 2); + } + return isoTable; + } + + /** + * Returns the language code of this Locale. + * + *

Note: ISO 639 is not a stable standard— some languages' codes have changed. + * Locale's constructor recognizes both the new and the old codes for the languages + * whose codes have changed, but this function always returns the old code. If you + * want to check for a specific language whose code has changed, don't do + *

+     * if (locale.getLanguage().equals("he")) // BAD!
+     *    ...
+     * 
+ * Instead, do + *
+     * if (locale.getLanguage().equals(new Locale("he").getLanguage()))
+     *    ...
+     * 
+ * @return The language code, or the empty string if none is defined. + * @see #getDisplayLanguage + */ + public String getLanguage() { + return baseLocale.getLanguage(); + } + + /** + * Returns the script for this locale, which should + * either be the empty string or an ISO 15924 4-letter script + * code. The first letter is uppercase and the rest are + * lowercase, for example, 'Latn', 'Cyrl'. + * + * @return The script code, or the empty string if none is defined. + * @see #getDisplayScript + * @since 1.7 + */ + public String getScript() { + return baseLocale.getScript(); + } + + /** + * Returns the country/region code for this locale, which should + * either be the empty string, an uppercase ISO 3166 2-letter code, + * or a UN M.49 3-digit code. + * + * @return The country/region code, or the empty string if none is defined. + * @see #getDisplayCountry + */ + public String getCountry() { + return baseLocale.getRegion(); + } + + /** + * Returns the variant code for this locale. + * + * @return The variant code, or the empty string if none is defined. + * @see #getDisplayVariant + */ + public String getVariant() { + return baseLocale.getVariant(); + } + + /** + * Returns the extension (or private use) value associated with + * the specified key, or null if there is no extension + * associated with the key. To be well-formed, the key must be one + * of [0-9A-Za-z]. Keys are case-insensitive, so + * for example 'z' and 'Z' represent the same extension. + * + * @param key the extension key + * @return The extension, or null if this locale defines no + * extension for the specified key. + * @throws IllegalArgumentException if key is not well-formed + * @see #PRIVATE_USE_EXTENSION + * @see #UNICODE_LOCALE_EXTENSION + * @since 1.7 + */ + public String getExtension(char key) { + if (!LocaleExtensions.isValidKey(key)) { + throw new IllegalArgumentException("Ill-formed extension key: " + key); + } + return (localeExtensions == null) ? null : localeExtensions.getExtensionValue(key); + } + + /** + * Returns the set of extension keys associated with this locale, or the + * empty set if it has no extensions. The returned set is unmodifiable. + * The keys will all be lower-case. + * + * @return The set of extension keys, or the empty set if this locale has + * no extensions. + * @since 1.7 + */ + public Set getExtensionKeys() { + if (localeExtensions == null) { + return Collections.emptySet(); + } + return localeExtensions.getKeys(); + } + + /** + * Returns the set of unicode locale attributes associated with + * this locale, or the empty set if it has no attributes. The + * returned set is unmodifiable. + * + * @return The set of attributes. + * @since 1.7 + */ + public Set getUnicodeLocaleAttributes() { + if (localeExtensions == null) { + return Collections.emptySet(); + } + return localeExtensions.getUnicodeLocaleAttributes(); + } + + /** + * Returns the Unicode locale type associated with the specified Unicode locale key + * for this locale. Returns the empty string for keys that are defined with no type. + * Returns null if the key is not defined. Keys are case-insensitive. The key must + * be two alphanumeric characters ([0-9a-zA-Z]), or an IllegalArgumentException is + * thrown. + * + * @param key the Unicode locale key + * @return The Unicode locale type associated with the key, or null if the + * locale does not define the key. + * @throws IllegalArgumentException if the key is not well-formed + * @throws NullPointerException if key is null + * @since 1.7 + */ + public String getUnicodeLocaleType(String key) { + if (!UnicodeLocaleExtension.isKey(key)) { + throw new IllegalArgumentException("Ill-formed Unicode locale key: " + key); + } + return (localeExtensions == null) ? null : localeExtensions.getUnicodeLocaleType(key); + } + + /** + * Returns the set of Unicode locale keys defined by this locale, or the empty set if + * this locale has none. The returned set is immutable. Keys are all lower case. + * + * @return The set of Unicode locale keys, or the empty set if this locale has + * no Unicode locale keywords. + * @since 1.7 + */ + public Set getUnicodeLocaleKeys() { + if (localeExtensions == null) { + return Collections.emptySet(); + } + return localeExtensions.getUnicodeLocaleKeys(); + } + + /** + * Package locale method returning the Locale's BaseLocale, + * used by ResourceBundle + * @return base locale of this Locale + */ + BaseLocale getBaseLocale() { + return baseLocale; + } + + /** + * Package private method returning the Locale's LocaleExtensions, + * used by ResourceBundle. + * @return locale exnteions of this Locale, + * or {@code null} if no extensions are defined + */ + LocaleExtensions getLocaleExtensions() { + return localeExtensions; + } + + /** + * Returns a string representation of this Locale + * object, consisting of language, country, variant, script, + * and extensions as below: + *

+ * language + "_" + country + "_" + (variant + "_#" | "#") + script + "-" + extensions + *
+ * + * Language is always lower case, country is always upper case, script is always title + * case, and extensions are always lower case. Extensions and private use subtags + * will be in canonical order as explained in {@link #toLanguageTag}. + * + *

When the locale has neither script nor extensions, the result is the same as in + * Java 6 and prior. + * + *

If both the language and country fields are missing, this function will return + * the empty string, even if the variant, script, or extensions field is present (you + * can't have a locale with just a variant, the variant must accompany a well-formed + * language or country code). + * + *

If script or extensions are present and variant is missing, no underscore is + * added before the "#". + * + *

This behavior is designed to support debugging and to be compatible with + * previous uses of toString that expected language, country, and variant + * fields only. To represent a Locale as a String for interchange purposes, use + * {@link #toLanguageTag}. + * + *

Examples:

    + *
  • en + *
  • de_DE + *
  • _GB + *
  • en_US_WIN + *
  • de__POSIX + *
  • zh_CN_#Hans + *
  • zh_TW_#Hant-x-java + *
  • th_TH_TH_#u-nu-thai
+ * + * @return A string representation of the Locale, for debugging. + * @see #getDisplayName + * @see #toLanguageTag + */ + @Override + public final String toString() { + boolean l = (baseLocale.getLanguage().length() != 0); + boolean s = (baseLocale.getScript().length() != 0); + boolean r = (baseLocale.getRegion().length() != 0); + boolean v = (baseLocale.getVariant().length() != 0); + boolean e = (localeExtensions != null && localeExtensions.getID().length() != 0); + + StringBuilder result = new StringBuilder(baseLocale.getLanguage()); + if (r || (l && (v || s || e))) { + result.append('_') + .append(baseLocale.getRegion()); // This may just append '_' + } + if (v && (l || r)) { + result.append('_') + .append(baseLocale.getVariant()); + } + + if (s && (l || r)) { + result.append("_#") + .append(baseLocale.getScript()); + } + + if (e && (l || r)) { + result.append('_'); + if (!s) { + result.append('#'); + } + result.append(localeExtensions.getID()); + } + + return result.toString(); + } + + /** + * Returns a well-formed IETF BCP 47 language tag representing + * this locale. + * + *

If this Locale has a language, country, or + * variant that does not satisfy the IETF BCP 47 language tag + * syntax requirements, this method handles these fields as + * described below: + * + *

Language: If language is empty, or not well-formed (for example "a" or + * "e2"), it will be emitted as "und" (Undetermined). + * + *

Country: If country is not well-formed (for example "12" or "USA"), + * it will be omitted. + * + *

Variant: If variant is well-formed, each sub-segment + * (delimited by '-' or '_') is emitted as a subtag. Otherwise: + *

    + * + *
  • if all sub-segments match [0-9a-zA-Z]{1,8} + * (for example "WIN" or "Oracle_JDK_Standard_Edition"), the first + * ill-formed sub-segment and all following will be appended to + * the private use subtag. The first appended subtag will be + * "lvariant", followed by the sub-segments in order, separated by + * hyphen. For example, "x-lvariant-WIN", + * "Oracle-x-lvariant-JDK-Standard-Edition". + * + *
  • if any sub-segment does not match + * [0-9a-zA-Z]{1,8}, the variant will be truncated + * and the problematic sub-segment and all following sub-segments + * will be omitted. If the remainder is non-empty, it will be + * emitted as a private use subtag as above (even if the remainder + * turns out to be well-formed). For example, + * "Solaris_isjustthecoolestthing" is emitted as + * "x-lvariant-Solaris", not as "solaris".
+ * + *

Special Conversions: Java supports some old locale + * representations, including deprecated ISO language codes, + * for compatibility. This method performs the following + * conversions: + *

    + * + *
  • Deprecated ISO language codes "iw", "ji", and "in" are + * converted to "he", "yi", and "id", respectively. + * + *
  • A locale with language "no", country "NO", and variant + * "NY", representing Norwegian Nynorsk (Norway), is converted + * to a language tag "nn-NO".
+ * + *

Note: Although the language tag created by this + * method is well-formed (satisfies the syntax requirements + * defined by the IETF BCP 47 specification), it is not + * necessarily a valid BCP 47 language tag. For example, + *

+     *   new Locale("xx", "YY").toLanguageTag();
+ * + * will return "xx-YY", but the language subtag "xx" and the + * region subtag "YY" are invalid because they are not registered + * in the IANA Language Subtag Registry. + * + * @return a BCP47 language tag representing the locale + * @see #forLanguageTag(String) + * @since 1.7 + */ + public String toLanguageTag() { + LanguageTag tag = LanguageTag.parseLocale(baseLocale, localeExtensions); + StringBuilder buf = new StringBuilder(); + + String subtag = tag.getLanguage(); + if (subtag.length() > 0) { + buf.append(LanguageTag.canonicalizeLanguage(subtag)); + } + + subtag = tag.getScript(); + if (subtag.length() > 0) { + buf.append(LanguageTag.SEP); + buf.append(LanguageTag.canonicalizeScript(subtag)); + } + + subtag = tag.getRegion(); + if (subtag.length() > 0) { + buf.append(LanguageTag.SEP); + buf.append(LanguageTag.canonicalizeRegion(subtag)); + } + + Listsubtags = tag.getVariants(); + for (String s : subtags) { + buf.append(LanguageTag.SEP); + // preserve casing + buf.append(s); + } + + subtags = tag.getExtensions(); + for (String s : subtags) { + buf.append(LanguageTag.SEP); + buf.append(LanguageTag.canonicalizeExtension(s)); + } + + subtag = tag.getPrivateuse(); + if (subtag.length() > 0) { + if (buf.length() > 0) { + buf.append(LanguageTag.SEP); + } + buf.append(LanguageTag.PRIVATEUSE).append(LanguageTag.SEP); + // preserve casing + buf.append(subtag); + } + + return buf.toString(); + } + + /** + * Returns a locale for the specified IETF BCP 47 language tag string. + * + *

If the specified language tag contains any ill-formed subtags, + * the first such subtag and all following subtags are ignored. Compare + * to {@link Locale.Builder#setLanguageTag} which throws an exception + * in this case. + * + *

The following conversions are performed:

    + * + *
  • The language code "und" is mapped to language "". + * + *
  • The language codes "he", "yi", and "id" are mapped to "iw", + * "ji", and "in" respectively. (This is the same canonicalization + * that's done in Locale's constructors.) + * + *
  • The portion of a private use subtag prefixed by "lvariant", + * if any, is removed and appended to the variant field in the + * result locale (without case normalization). If it is then + * empty, the private use subtag is discarded: + * + *
    +     *     Locale loc;
    +     *     loc = Locale.forLanguageTag("en-US-x-lvariant-POSIX");
    +     *     loc.getVariant(); // returns "POSIX"
    +     *     loc.getExtension('x'); // returns null
    +     *
    +     *     loc = Locale.forLanguageTag("de-POSIX-x-URP-lvariant-Abc-Def");
    +     *     loc.getVariant(); // returns "POSIX_Abc_Def"
    +     *     loc.getExtension('x'); // returns "urp"
    +     * 
    + * + *
  • When the languageTag argument contains an extlang subtag, + * the first such subtag is used as the language, and the primary + * language subtag and other extlang subtags are ignored: + * + *
    +     *     Locale.forLanguageTag("ar-aao").getLanguage(); // returns "aao"
    +     *     Locale.forLanguageTag("en-abc-def-us").toString(); // returns "abc_US"
    +     * 
    + * + *
  • Case is normalized except for variant tags, which are left + * unchanged. Language is normalized to lower case, script to + * title case, country to upper case, and extensions to lower + * case. + * + *
  • If, after processing, the locale would exactly match either + * ja_JP_JP or th_TH_TH with no extensions, the appropriate + * extensions are added as though the constructor had been called: + * + *
    +     *    Locale.forLanguageTag("ja-JP-x-lvariant-JP").toLanguageTag();
    +     *    // returns "ja-JP-u-ca-japanese-x-lvariant-JP"
    +     *    Locale.forLanguageTag("th-TH-x-lvariant-TH").toLanguageTag();
    +     *    // returns "th-TH-u-nu-thai-x-lvariant-TH"
    +     * 
+ * + *

This implements the 'Language-Tag' production of BCP47, and + * so supports grandfathered (regular and irregular) as well as + * private use language tags. Stand alone private use tags are + * represented as empty language and extension 'x-whatever', + * and grandfathered tags are converted to their canonical replacements + * where they exist. + * + *

Grandfathered tags with canonical replacements are as follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
grandfathered tag modern replacement
art-lojban jbo
i-ami ami
i-bnn bnn
i-hak hak
i-klingon tlh
i-lux lb
i-navajo nv
i-pwn pwn
i-tao tao
i-tay tay
i-tsu tsu
no-bok nb
no-nyn nn
sgn-BE-FR sfb
sgn-BE-NL vgt
sgn-CH-DE sgg
zh-guoyu cmn
zh-hakka hak
zh-min-nan nan
zh-xiang hsn
+ * + *

Grandfathered tags with no modern replacement will be + * converted as follows: + * + * + * + * + * + * + * + * + * + * + * + *
grandfathered tag converts to
cel-gaulish xtg-x-cel-gaulish
en-GB-oed en-GB-x-oed
i-default en-x-i-default
i-enochian und-x-i-enochian
i-mingo see-x-i-mingo
zh-min nan-x-zh-min
+ * + *

For a list of all grandfathered tags, see the + * IANA Language Subtag Registry (search for "Type: grandfathered"). + * + *

Note: there is no guarantee that toLanguageTag + * and forLanguageTag will round-trip. + * + * @param languageTag the language tag + * @return The locale that best represents the language tag. + * @throws NullPointerException if languageTag is null + * @see #toLanguageTag() + * @see java.util.Locale.Builder#setLanguageTag(String) + * @since 1.7 + */ + public static Locale forLanguageTag(String languageTag) { + LanguageTag tag = LanguageTag.parse(languageTag, null); + InternalLocaleBuilder bldr = new InternalLocaleBuilder(); + bldr.setLanguageTag(tag); + BaseLocale base = bldr.getBaseLocale(); + LocaleExtensions exts = bldr.getLocaleExtensions(); + if (exts == null && base.getVariant().length() > 0) { + exts = getCompatibilityExtensions(base.getLanguage(), base.getScript(), + base.getRegion(), base.getVariant()); + } + return getInstance(base, exts); + } + + /** + * Returns a three-letter abbreviation of this locale's language. + * If the language matches an ISO 639-1 two-letter code, the + * corresponding ISO 639-2/T three-letter lowercase code is + * returned. The ISO 639-2 language codes can be found on-line, + * see "Codes for the Representation of Names of Languages Part 2: + * Alpha-3 Code". If the locale specifies a three-letter + * language, the language is returned as is. If the locale does + * not specify a language the empty string is returned. + * + * @return A three-letter abbreviation of this locale's language. + * @exception MissingResourceException Throws MissingResourceException if + * three-letter language abbreviation is not available for this locale. + */ + public String getISO3Language() throws MissingResourceException { + String lang = baseLocale.getLanguage(); + if (lang.length() == 3) { + return lang; + } + + String language3 = getISO3Code(lang, LocaleISOData.isoLanguageTable); + if (language3 == null) { + throw new MissingResourceException("Couldn't find 3-letter language code for " + + lang, "FormatData_" + toString(), "ShortLanguage"); + } + return language3; + } + + /** + * Returns a three-letter abbreviation for this locale's country. + * If the country matches an ISO 3166-1 alpha-2 code, the + * corresponding ISO 3166-1 alpha-3 uppercase code is returned. + * If the locale doesn't specify a country, this will be the empty + * string. + * + *

The ISO 3166-1 codes can be found on-line. + * + * @return A three-letter abbreviation of this locale's country. + * @exception MissingResourceException Throws MissingResourceException if the + * three-letter country abbreviation is not available for this locale. + */ + public String getISO3Country() throws MissingResourceException { + String country3 = getISO3Code(baseLocale.getRegion(), LocaleISOData.isoCountryTable); + if (country3 == null) { + throw new MissingResourceException("Couldn't find 3-letter country code for " + + baseLocale.getRegion(), "FormatData_" + toString(), "ShortCountry"); + } + return country3; + } + + private static final String getISO3Code(String iso2Code, String table) { + int codeLength = iso2Code.length(); + if (codeLength == 0) { + return ""; + } + + int tableLength = table.length(); + int index = tableLength; + if (codeLength == 2) { + char c1 = iso2Code.charAt(0); + char c2 = iso2Code.charAt(1); + for (index = 0; index < tableLength; index += 5) { + if (table.charAt(index) == c1 + && table.charAt(index + 1) == c2) { + break; + } + } + } + return index < tableLength ? table.substring(index + 2, index + 5) : null; + } + + /** + * Returns a name for the locale's language that is appropriate for display to the + * user. + * If possible, the name returned will be localized for the default locale. + * For example, if the locale is fr_FR and the default locale + * is en_US, getDisplayLanguage() will return "French"; if the locale is en_US and + * the default locale is fr_FR, getDisplayLanguage() will return "anglais". + * If the name returned cannot be localized for the default locale, + * (say, we don't have a Japanese name for Croatian), + * this function falls back on the English name, and uses the ISO code as a last-resort + * value. If the locale doesn't specify a language, this function returns the empty string. + */ + public final String getDisplayLanguage() { + return getDisplayLanguage(getDefault(Category.DISPLAY)); + } + + /** + * Returns a name for the locale's language that is appropriate for display to the + * user. + * If possible, the name returned will be localized according to inLocale. + * For example, if the locale is fr_FR and inLocale + * is en_US, getDisplayLanguage() will return "French"; if the locale is en_US and + * inLocale is fr_FR, getDisplayLanguage() will return "anglais". + * If the name returned cannot be localized according to inLocale, + * (say, we don't have a Japanese name for Croatian), + * this function falls back on the English name, and finally + * on the ISO code as a last-resort value. If the locale doesn't specify a language, + * this function returns the empty string. + * + * @exception NullPointerException if inLocale is null + */ + public String getDisplayLanguage(Locale inLocale) { + return getDisplayString(baseLocale.getLanguage(), inLocale, DISPLAY_LANGUAGE); + } + + /** + * Returns a name for the the locale's script that is appropriate for display to + * the user. If possible, the name will be localized for the default locale. Returns + * the empty string if this locale doesn't specify a script code. + * + * @return the display name of the script code for the current default locale + * @since 1.7 + */ + public String getDisplayScript() { + return getDisplayScript(getDefault()); + } + + /** + * Returns a name for the locale's script that is appropriate + * for display to the user. If possible, the name will be + * localized for the given locale. Returns the empty string if + * this locale doesn't specify a script code. + * + * @return the display name of the script code for the current default locale + * @throws NullPointerException if inLocale is null + * @since 1.7 + */ + public String getDisplayScript(Locale inLocale) { + return getDisplayString(baseLocale.getScript(), inLocale, DISPLAY_SCRIPT); + } + + /** + * Returns a name for the locale's country that is appropriate for display to the + * user. + * If possible, the name returned will be localized for the default locale. + * For example, if the locale is fr_FR and the default locale + * is en_US, getDisplayCountry() will return "France"; if the locale is en_US and + * the default locale is fr_FR, getDisplayCountry() will return "Etats-Unis". + * If the name returned cannot be localized for the default locale, + * (say, we don't have a Japanese name for Croatia), + * this function falls back on the English name, and uses the ISO code as a last-resort + * value. If the locale doesn't specify a country, this function returns the empty string. + */ + public final String getDisplayCountry() { + return getDisplayCountry(getDefault(Category.DISPLAY)); + } + + /** + * Returns a name for the locale's country that is appropriate for display to the + * user. + * If possible, the name returned will be localized according to inLocale. + * For example, if the locale is fr_FR and inLocale + * is en_US, getDisplayCountry() will return "France"; if the locale is en_US and + * inLocale is fr_FR, getDisplayCountry() will return "Etats-Unis". + * If the name returned cannot be localized according to inLocale. + * (say, we don't have a Japanese name for Croatia), + * this function falls back on the English name, and finally + * on the ISO code as a last-resort value. If the locale doesn't specify a country, + * this function returns the empty string. + * + * @exception NullPointerException if inLocale is null + */ + public String getDisplayCountry(Locale inLocale) { + return getDisplayString(baseLocale.getRegion(), inLocale, DISPLAY_COUNTRY); + } + + private String getDisplayString(String code, Locale inLocale, int type) { + if (code.length() == 0) { + return ""; + } + + if (inLocale == null) { + throw new NullPointerException(); + } + + try { + OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale); + String key = (type == DISPLAY_VARIANT ? "%%"+code : code); + String result = null; + + // Check whether a provider can provide an implementation that's closer + // to the requested locale than what the Java runtime itself can provide. + LocaleServiceProviderPool pool = + LocaleServiceProviderPool.getPool(LocaleNameProvider.class); + if (pool.hasProviders()) { + result = pool.getLocalizedObject( + LocaleNameGetter.INSTANCE, + inLocale, bundle, key, + type, code); + } + + if (result == null) { + result = bundle.getString(key); + } + + if (result != null) { + return result; + } + } + catch (Exception e) { + // just fall through + } + return code; + } + + /** + * Returns a name for the locale's variant code that is appropriate for display to the + * user. If possible, the name will be localized for the default locale. If the locale + * doesn't specify a variant code, this function returns the empty string. + */ + public final String getDisplayVariant() { + return getDisplayVariant(getDefault(Category.DISPLAY)); + } + + /** + * Returns a name for the locale's variant code that is appropriate for display to the + * user. If possible, the name will be localized for inLocale. If the locale + * doesn't specify a variant code, this function returns the empty string. + * + * @exception NullPointerException if inLocale is null + */ + public String getDisplayVariant(Locale inLocale) { + if (baseLocale.getVariant().length() == 0) + return ""; + + OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale); + + String names[] = getDisplayVariantArray(bundle, inLocale); + + // Get the localized patterns for formatting a list, and use + // them to format the list. + String listPattern = null; + String listCompositionPattern = null; + try { + listPattern = bundle.getString("ListPattern"); + listCompositionPattern = bundle.getString("ListCompositionPattern"); + } catch (MissingResourceException e) { + } + return formatList(names, listPattern, listCompositionPattern); + } + + /** + * Returns a name for the locale that is appropriate for display to the + * user. This will be the values returned by getDisplayLanguage(), + * getDisplayScript(), getDisplayCountry(), and getDisplayVariant() assembled + * into a single string. The the non-empty values are used in order, + * with the second and subsequent names in parentheses. For example: + *

+ * language (script, country, variant)
+ * language (country)
+ * language (variant)
+ * script (country)
+ * country
+ *
+ * depending on which fields are specified in the locale. If the + * language, sacript, country, and variant fields are all empty, + * this function returns the empty string. + */ + public final String getDisplayName() { + return getDisplayName(getDefault(Category.DISPLAY)); + } + + /** + * Returns a name for the locale that is appropriate for display + * to the user. This will be the values returned by + * getDisplayLanguage(), getDisplayScript(),getDisplayCountry(), + * and getDisplayVariant() assembled into a single string. + * The non-empty values are used in order, + * with the second and subsequent names in parentheses. For example: + *
+ * language (script, country, variant)
+ * language (country)
+ * language (variant)
+ * script (country)
+ * country
+ *
+ * depending on which fields are specified in the locale. If the + * language, script, country, and variant fields are all empty, + * this function returns the empty string. + * + * @throws NullPointerException if inLocale is null + */ + public String getDisplayName(Locale inLocale) { + OpenListResourceBundle bundle = LocaleData.getLocaleNames(inLocale); + + String languageName = getDisplayLanguage(inLocale); + String scriptName = getDisplayScript(inLocale); + String countryName = getDisplayCountry(inLocale); + String[] variantNames = getDisplayVariantArray(bundle, inLocale); + + // Get the localized patterns for formatting a display name. + String displayNamePattern = null; + String listPattern = null; + String listCompositionPattern = null; + try { + displayNamePattern = bundle.getString("DisplayNamePattern"); + listPattern = bundle.getString("ListPattern"); + listCompositionPattern = bundle.getString("ListCompositionPattern"); + } catch (MissingResourceException e) { + } + + // The display name consists of a main name, followed by qualifiers. + // Typically, the format is "MainName (Qualifier, Qualifier)" but this + // depends on what pattern is stored in the display locale. + String mainName = null; + String[] qualifierNames = null; + + // The main name is the language, or if there is no language, the script, + // then if no script, the country. If there is no language/script/country + // (an anomalous situation) then the display name is simply the variant's + // display name. + if (languageName.length() == 0 && scriptName.length() == 0 && countryName.length() == 0) { + if (variantNames.length == 0) { + return ""; + } else { + return formatList(variantNames, listPattern, listCompositionPattern); + } + } + ArrayList names = new ArrayList<>(4); + if (languageName.length() != 0) { + names.add(languageName); + } + if (scriptName.length() != 0) { + names.add(scriptName); + } + if (countryName.length() != 0) { + names.add(countryName); + } + if (variantNames.length != 0) { + for (String var : variantNames) { + names.add(var); + } + } + + // The first one in the main name + mainName = names.get(0); + + // Others are qualifiers + int numNames = names.size(); + qualifierNames = (numNames > 1) ? + names.subList(1, numNames).toArray(new String[numNames - 1]) : new String[0]; + + // Create an array whose first element is the number of remaining + // elements. This serves as a selector into a ChoiceFormat pattern from + // the resource. The second and third elements are the main name and + // the qualifier; if there are no qualifiers, the third element is + // unused by the format pattern. + Object[] displayNames = { + new Integer(qualifierNames.length != 0 ? 2 : 1), + mainName, + // We could also just call formatList() and have it handle the empty + // list case, but this is more efficient, and we want it to be + // efficient since all the language-only locales will not have any + // qualifiers. + qualifierNames.length != 0 ? formatList(qualifierNames, listPattern, listCompositionPattern) : null + }; + + if (displayNamePattern != null) { + return new MessageFormat(displayNamePattern).format(displayNames); + } + else { + // If we cannot get the message format pattern, then we use a simple + // hard-coded pattern. This should not occur in practice unless the + // installation is missing some core files (FormatData etc.). + StringBuilder result = new StringBuilder(); + result.append((String)displayNames[1]); + if (displayNames.length > 2) { + result.append(" ("); + result.append((String)displayNames[2]); + result.append(')'); + } + return result.toString(); + } + } + + /** + * Overrides Cloneable. + */ + public Object clone() + { + try { + Locale that = (Locale)super.clone(); + return that; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Override hashCode. + * Since Locales are often used in hashtables, caches the value + * for speed. + */ + @Override + public int hashCode() { + int hc = hashCodeValue; + if (hc == 0) { + hc = baseLocale.hashCode(); + if (localeExtensions != null) { + hc ^= localeExtensions.hashCode(); + } + hashCodeValue = hc; + } + return hc; + } + + // Overrides + + /** + * Returns true if this Locale is equal to another object. A Locale is + * deemed equal to another Locale with identical language, script, country, + * variant and extensions, and unequal to all other objects. + * + * @return true if this Locale is equal to the specified object. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) // quick check + return true; + if (!(obj instanceof Locale)) + return false; + BaseLocale otherBase = ((Locale)obj).baseLocale; + if (!baseLocale.equals(otherBase)) { + return false; + } + if (localeExtensions == null) { + return ((Locale)obj).localeExtensions == null; + } + return localeExtensions.equals(((Locale)obj).localeExtensions); + } + + // ================= privates ===================================== + + private transient BaseLocale baseLocale; + private transient LocaleExtensions localeExtensions; + + /** + * Calculated hashcode + */ + private transient volatile int hashCodeValue = 0; + + private static Locale defaultLocale = null; + private static Locale defaultDisplayLocale = null; + private static Locale defaultFormatLocale = null; + + /** + * Return an array of the display names of the variant. + * @param bundle the ResourceBundle to use to get the display names + * @return an array of display names, possible of zero length. + */ + private String[] getDisplayVariantArray(OpenListResourceBundle bundle, Locale inLocale) { + // Split the variant name into tokens separated by '_'. + StringTokenizer tokenizer = new StringTokenizer(baseLocale.getVariant(), "_"); + String[] names = new String[tokenizer.countTokens()]; + + // For each variant token, lookup the display name. If + // not found, use the variant name itself. + for (int i=0; i0) result.append(','); + result.append(stringList[i]); + } + return result.toString(); + } + + // Compose the list down to three elements if necessary + if (stringList.length > 3) { + MessageFormat format = new MessageFormat(listCompositionPattern); + stringList = composeList(format, stringList); + } + + // Rebuild the argument list with the list length as the first element + Object[] args = new Object[stringList.length + 1]; + System.arraycopy(stringList, 0, args, 1, stringList.length); + args[0] = new Integer(stringList.length); + + // Format it using the pattern in the resource + MessageFormat format = new MessageFormat(listPattern); + return format.format(args); + } + + /** + * Given a list of strings, return a list shortened to three elements. + * Shorten it by applying the given format to the first two elements + * recursively. + * @param format a format which takes two arguments + * @param list a list of strings + * @return if the list is three elements or shorter, the same list; + * otherwise, a new list of three elements. + */ + private static String[] composeList(MessageFormat format, String[] list) { + if (list.length <= 3) return list; + + // Use the given format to compose the first two elements into one + String[] listItems = { list[0], list[1] }; + String newItem = format.format(listItems); + + // Form a new list one element shorter + String[] newList = new String[list.length-1]; + System.arraycopy(list, 2, newList, 1, newList.length-1); + newList[0] = newItem; + + // Recurse + return composeList(format, newList); + } + + /** + * @serialField language String + * language subtag in lower case. (See getLanguage()) + * @serialField country String + * country subtag in upper case. (See getCountry()) + * @serialField variant String + * variant subtags separated by LOWLINE characters. (See getVariant()) + * @serialField hashcode int + * deprecated, for forward compatibility only + * @serialField script String + * script subtag in title case (See getScript()) + * @serialField extensions String + * canonical representation of extensions, that is, + * BCP47 extensions in alphabetical order followed by + * BCP47 private use subtags, all in lower case letters + * separated by HYPHEN-MINUS characters. + * (See getExtensionKeys(), + * getExtension(char)) + */ + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("language", String.class), + new ObjectStreamField("country", String.class), + new ObjectStreamField("variant", String.class), + new ObjectStreamField("hashcode", int.class), + new ObjectStreamField("script", String.class), + new ObjectStreamField("extensions", String.class), + }; + + /** + * Serializes this Locale to the specified ObjectOutputStream. + * @param out the ObjectOutputStream to write + * @throws IOException + * @since 1.7 + */ + private void writeObject(ObjectOutputStream out) throws IOException { + ObjectOutputStream.PutField fields = out.putFields(); + fields.put("language", baseLocale.getLanguage()); + fields.put("script", baseLocale.getScript()); + fields.put("country", baseLocale.getRegion()); + fields.put("variant", baseLocale.getVariant()); + fields.put("extensions", localeExtensions == null ? "" : localeExtensions.getID()); + fields.put("hashcode", -1); // place holder just for backward support + out.writeFields(); + } + + /** + * Deserializes this Locale. + * @param in the ObjectInputStream to read + * @throws IOException + * @throws ClassNotFoundException + * @throws IllformdLocaleException + * @since 1.7 + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + ObjectInputStream.GetField fields = in.readFields(); + String language = (String)fields.get("language", ""); + String script = (String)fields.get("script", ""); + String country = (String)fields.get("country", ""); + String variant = (String)fields.get("variant", ""); + String extStr = (String)fields.get("extensions", ""); + baseLocale = BaseLocale.getInstance(convertOldISOCodes(language), script, country, variant); + if (extStr.length() > 0) { + try { + InternalLocaleBuilder bldr = new InternalLocaleBuilder(); + bldr.setExtensions(extStr); + localeExtensions = bldr.getLocaleExtensions(); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage()); + } + } else { + localeExtensions = null; + } + } + + /** + * Returns a cached Locale instance equivalent to + * the deserialized Locale. When serialized + * language, country and variant fields read from the object data stream + * are exactly "ja", "JP", "JP" or "th", "TH", "TH" and script/extensions + * fields are empty, this method supplies UNICODE_LOCALE_EXTENSION + * "ca"/"japanese" (calendar type is "japanese") or "nu"/"thai" (number script + * type is "thai"). See Special Cases + * for more information. + * + * @return an instance of Locale equivalent to + * the deserialized Locale. + * @throws java.io.ObjectStreamException + */ + private Object readResolve() throws java.io.ObjectStreamException { + return getInstance(baseLocale.getLanguage(), baseLocale.getScript(), + baseLocale.getRegion(), baseLocale.getVariant(), localeExtensions); + } + + private static volatile String[] isoLanguages = null; + + private static volatile String[] isoCountries = null; + + private static String convertOldISOCodes(String language) { + // we accept both the old and the new ISO codes for the languages whose ISO + // codes have changed, but we always store the OLD code, for backward compatibility + language = LocaleUtils.toLowerString(language).intern(); + if (language == "he") { + return "iw"; + } else if (language == "yi") { + return "ji"; + } else if (language == "id") { + return "in"; + } else { + return language; + } + } + + private static LocaleExtensions getCompatibilityExtensions(String language, + String script, + String country, + String variant) { + LocaleExtensions extensions = null; + // Special cases for backward compatibility support + if (LocaleUtils.caseIgnoreMatch(language, "ja") + && script.length() == 0 + && LocaleUtils.caseIgnoreMatch(country, "jp") + && "JP".equals(variant)) { + // ja_JP_JP -> u-ca-japanese (calendar = japanese) + extensions = LocaleExtensions.CALENDAR_JAPANESE; + } else if (LocaleUtils.caseIgnoreMatch(language, "th") + && script.length() == 0 + && LocaleUtils.caseIgnoreMatch(country, "th") + && "TH".equals(variant)) { + // th_TH_TH -> u-nu-thai (numbersystem = thai) + extensions = LocaleExtensions.NUMBER_THAI; + } + return extensions; + } + + /** + * Obtains a localized locale names from a LocaleNameProvider + * implementation. + */ + private static class LocaleNameGetter + implements LocaleServiceProviderPool.LocalizedObjectGetter { + private static final LocaleNameGetter INSTANCE = new LocaleNameGetter(); + + public String getObject(LocaleNameProvider localeNameProvider, + Locale locale, + String key, + Object... params) { + assert params.length == 2; + int type = (Integer)params[0]; + String code = (String)params[1]; + + switch(type) { + case DISPLAY_LANGUAGE: + return localeNameProvider.getDisplayLanguage(code, locale); + case DISPLAY_COUNTRY: + return localeNameProvider.getDisplayCountry(code, locale); + case DISPLAY_VARIANT: + return localeNameProvider.getDisplayVariant(code, locale); + case DISPLAY_SCRIPT: + return localeNameProvider.getDisplayScript(code, locale); + default: + assert false; // shouldn't happen + } + + return null; + } + } + + /** + * Enum for locale categories. These locale categories are used to get/set + * the default locale for the specific functionality represented by the + * category. + * + * @see #getDefault(Locale.Category) + * @see #setDefault(Locale.Category, Locale) + * @since 1.7 + */ + public enum Category { + + /** + * Category used to represent the default locale for + * displaying user interfaces. + */ + DISPLAY("user.language.display", + "user.script.display", + "user.country.display", + "user.variant.display"), + + /** + * Category used to represent the default locale for + * formatting dates, numbers, and/or currencies. + */ + FORMAT("user.language.format", + "user.script.format", + "user.country.format", + "user.variant.format"); + + Category(String languageKey, String scriptKey, String countryKey, String variantKey) { + this.languageKey = languageKey; + this.scriptKey = scriptKey; + this.countryKey = countryKey; + this.variantKey = variantKey; + } + + final String languageKey; + final String scriptKey; + final String countryKey; + final String variantKey; + } + + /** + * Builder is used to build instances of Locale + * from values configured by the setters. Unlike the Locale + * constructors, the Builder checks if a value configured by a + * setter satisfies the syntax requirements defined by the Locale + * class. A Locale object created by a Builder is + * well-formed and can be transformed to a well-formed IETF BCP 47 language tag + * without losing information. + * + *

Note: The Locale class does not provide any + * syntactic restrictions on variant, while BCP 47 requires each variant + * subtag to be 5 to 8 alphanumerics or a single numeric followed by 3 + * alphanumerics. The method setVariant throws + * IllformedLocaleException for a variant that does not satisfy + * this restriction. If it is necessary to support such a variant, use a + * Locale constructor. However, keep in mind that a Locale + * object created this way might lose the variant information when + * transformed to a BCP 47 language tag. + * + *

The following example shows how to create a Locale object + * with the Builder. + *

+ *
+     *     Locale aLocale = new Builder().setLanguage("sr").setScript("Latn").setRegion("RS").build();
+     * 
+ *
+ * + *

Builders can be reused; clear() resets all + * fields to their default values. + * + * @see Locale#forLanguageTag + * @since 1.7 + */ + public static final class Builder { + private final InternalLocaleBuilder localeBuilder; + + /** + * Constructs an empty Builder. The default value of all + * fields, extensions, and private use information is the + * empty string. + */ + public Builder() { + localeBuilder = new InternalLocaleBuilder(); + } + + /** + * Resets the Builder to match the provided + * locale. Existing state is discarded. + * + *

All fields of the locale must be well-formed, see {@link Locale}. + * + *

Locales with any ill-formed fields cause + * IllformedLocaleException to be thrown, except for the + * following three cases which are accepted for compatibility + * reasons:

    + *
  • Locale("ja", "JP", "JP") is treated as "ja-JP-u-ca-japanese" + *
  • Locale("th", "TH", "TH") is treated as "th-TH-u-nu-thai" + *
  • Locale("no", "NO", "NY") is treated as "nn-NO"
+ * + * @param locale the locale + * @return This builder. + * @throws IllformedLocaleException if locale has + * any ill-formed fields. + * @throws NullPointerException if locale is null. + */ + public Builder setLocale(Locale locale) { + try { + localeBuilder.setLocale(locale.baseLocale, locale.localeExtensions); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Resets the Builder to match the provided IETF BCP 47 + * language tag. Discards the existing state. Null and the + * empty string cause the builder to be reset, like {@link + * #clear}. Grandfathered tags (see {@link + * Locale#forLanguageTag}) are converted to their canonical + * form before being processed. Otherwise, the language tag + * must be well-formed (see {@link Locale}) or an exception is + * thrown (unlike Locale.forLanguageTag, which + * just discards ill-formed and following portions of the + * tag). + * + * @param languageTag the language tag + * @return This builder. + * @throws IllformedLocaleException if languageTag is ill-formed + * @see Locale#forLanguageTag(String) + */ + public Builder setLanguageTag(String languageTag) { + ParseStatus sts = new ParseStatus(); + LanguageTag tag = LanguageTag.parse(languageTag, sts); + if (sts.isError()) { + throw new IllformedLocaleException(sts.getErrorMessage(), sts.getErrorIndex()); + } + localeBuilder.setLanguageTag(tag); + return this; + } + + /** + * Sets the language. If language is the empty string or + * null, the language in this Builder is removed. Otherwise, + * the language must be well-formed + * or an exception is thrown. + * + *

The typical language value is a two or three-letter language + * code as defined in ISO639. + * + * @param language the language + * @return This builder. + * @throws IllformedLocaleException if language is ill-formed + */ + public Builder setLanguage(String language) { + try { + localeBuilder.setLanguage(language); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Sets the script. If script is null or the empty string, + * the script in this Builder is removed. + * Otherwise, the script must be well-formed or an + * exception is thrown. + * + *

The typical script value is a four-letter script code as defined by ISO 15924. + * + * @param script the script + * @return This builder. + * @throws IllformedLocaleException if script is ill-formed + */ + public Builder setScript(String script) { + try { + localeBuilder.setScript(script); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Sets the region. If region is null or the empty string, the region + * in this Builder is removed. Otherwise, + * the region must be well-formed or an + * exception is thrown. + * + *

The typical region value is a two-letter ISO 3166 code or a + * three-digit UN M.49 area code. + * + *

The country value in the Locale created by the + * Builder is always normalized to upper case. + * + * @param region the region + * @return This builder. + * @throws IllformedLocaleException if region is ill-formed + */ + public Builder setRegion(String region) { + try { + localeBuilder.setRegion(region); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Sets the variant. If variant is null or the empty string, the + * variant in this Builder is removed. Otherwise, it + * must consist of one or more well-formed + * subtags, or an exception is thrown. + * + *

Note: This method checks if variant + * satisfies the IETF BCP 47 variant subtag's syntax requirements, + * and normalizes the value to lowercase letters. However, + * the Locale class does not impose any syntactic + * restriction on variant, and the variant value in + * Locale is case sensitive. To set such a variant, + * use a Locale constructor. + * + * @param variant the variant + * @return This builder. + * @throws IllformedLocaleException if variant is ill-formed + */ + public Builder setVariant(String variant) { + try { + localeBuilder.setVariant(variant); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Sets the extension for the given key. If the value is null or the + * empty string, the extension is removed. Otherwise, the extension + * must be well-formed or an exception + * is thrown. + * + *

Note: The key {@link Locale#UNICODE_LOCALE_EXTENSION + * UNICODE_LOCALE_EXTENSION} ('u') is used for the Unicode locale extension. + * Setting a value for this key replaces any existing Unicode locale key/type + * pairs with those defined in the extension. + * + *

Note: The key {@link Locale#PRIVATE_USE_EXTENSION + * PRIVATE_USE_EXTENSION} ('x') is used for the private use code. To be + * well-formed, the value for this key needs only to have subtags of one to + * eight alphanumeric characters, not two to eight as in the general case. + * + * @param key the extension key + * @param value the extension value + * @return This builder. + * @throws IllformedLocaleException if key is illegal + * or value is ill-formed + * @see #setUnicodeLocaleKeyword(String, String) + */ + public Builder setExtension(char key, String value) { + try { + localeBuilder.setExtension(key, value); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Sets the Unicode locale keyword type for the given key. If the type + * is null, the Unicode keyword is removed. Otherwise, the key must be + * non-null and both key and type must be well-formed or an exception + * is thrown. + * + *

Keys and types are converted to lower case. + * + *

Note:Setting the 'u' extension via {@link #setExtension} + * replaces all Unicode locale keywords with those defined in the + * extension. + * + * @param key the Unicode locale key + * @param type the Unicode locale type + * @return This builder. + * @throws IllformedLocaleException if key or type + * is ill-formed + * @throws NullPointerException if key is null + * @see #setExtension(char, String) + */ + public Builder setUnicodeLocaleKeyword(String key, String type) { + try { + localeBuilder.setUnicodeLocaleKeyword(key, type); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Adds a unicode locale attribute, if not already present, otherwise + * has no effect. The attribute must not be null and must be well-formed or an exception + * is thrown. + * + * @param attribute the attribute + * @return This builder. + * @throws NullPointerException if attribute is null + * @throws IllformedLocaleException if attribute is ill-formed + * @see #setExtension(char, String) + */ + public Builder addUnicodeLocaleAttribute(String attribute) { + try { + localeBuilder.addUnicodeLocaleAttribute(attribute); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Removes a unicode locale attribute, if present, otherwise has no + * effect. The attribute must not be null and must be well-formed or an exception + * is thrown. + * + *

Attribute comparision for removal is case-insensitive. + * + * @param attribute the attribute + * @return This builder. + * @throws NullPointerException if attribute is null + * @throws IllformedLocaleException if attribute is ill-formed + * @see #setExtension(char, String) + */ + public Builder removeUnicodeLocaleAttribute(String attribute) { + try { + localeBuilder.removeUnicodeLocaleAttribute(attribute); + } catch (LocaleSyntaxException e) { + throw new IllformedLocaleException(e.getMessage(), e.getErrorIndex()); + } + return this; + } + + /** + * Resets the builder to its initial, empty state. + * + * @return This builder. + */ + public Builder clear() { + localeBuilder.clear(); + return this; + } + + /** + * Resets the extensions to their initial, empty state. + * Language, script, region and variant are unchanged. + * + * @return This builder. + * @see #setExtension(char, String) + */ + public Builder clearExtensions() { + localeBuilder.clearExtensions(); + return this; + } + + /** + * Returns an instance of Locale created from the fields set + * on this builder. + * + *

This applies the conversions listed in {@link Locale#forLanguageTag} + * when constructing a Locale. (Grandfathered tags are handled in + * {@link #setLanguageTag}.) + * + * @return A Locale. + */ + public Locale build() { + BaseLocale baseloc = localeBuilder.getBaseLocale(); + LocaleExtensions extensions = localeBuilder.getLocaleExtensions(); + if (extensions == null && baseloc.getVariant().length() > 0) { + extensions = getCompatibilityExtensions(baseloc.getLanguage(), baseloc.getScript(), + baseloc.getRegion(), baseloc.getVariant()); + } + return Locale.getInstance(baseloc, extensions); + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/MissingResourceException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/MissingResourceException.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,124 @@ +/* + * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +/** + * Signals that a resource is missing. + * @see java.lang.Exception + * @see ResourceBundle + * @author Mark Davis + * @since JDK1.1 + */ +public +class MissingResourceException extends RuntimeException { + + /** + * Constructs a MissingResourceException with the specified information. + * A detail message is a String that describes this particular exception. + * @param s the detail message + * @param className the name of the resource class + * @param key the key for the missing resource. + */ + public MissingResourceException(String s, String className, String key) { + super(s); + this.className = className; + this.key = key; + } + + /** + * Constructs a MissingResourceException with + * message, className, key, + * and cause. This constructor is package private for + * use by ResourceBundle.getBundle. + * + * @param message + * the detail message + * @param className + * the name of the resource class + * @param key + * the key for the missing resource. + * @param cause + * the cause (which is saved for later retrieval by the + * {@link Throwable.getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent + * or unknown.) + */ + MissingResourceException(String message, String className, String key, Throwable cause) { + super(message, cause); + this.className = className; + this.key = key; + } + + /** + * Gets parameter passed by constructor. + * + * @return the name of the resource class + */ + public String getClassName() { + return className; + } + + /** + * Gets parameter passed by constructor. + * + * @return the key for the missing resource + */ + public String getKey() { + return key; + } + + //============ privates ============ + + // serialization compatibility with JDK1.1 + private static final long serialVersionUID = -4876345176062000401L; + + /** + * The class name of the resource bundle requested by the user. + * @serial + */ + private String className; + + /** + * The name of the specific resource requested by the user. + * @serial + */ + private String key; +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/Properties.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/Properties.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,1114 @@ +/* + * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.BufferedWriter; + +/** + * The Properties class represents a persistent set of + * properties. The Properties can be saved to a stream + * or loaded from a stream. Each key and its corresponding value in + * the property list is a string. + *

+ * A property list can contain another property list as its + * "defaults"; this second property list is searched if + * the property key is not found in the original property list. + *

+ * Because Properties inherits from Hashtable, the + * put and putAll methods can be applied to a + * Properties object. Their use is strongly discouraged as they + * allow the caller to insert entries whose keys or values are not + * Strings. The setProperty method should be used + * instead. If the store or save method is called + * on a "compromised" Properties object that contains a + * non-String key or value, the call will fail. Similarly, + * the call to the propertyNames or list method + * will fail if it is called on a "compromised" Properties + * object that contains a non-String key. + * + *

+ * The {@link #load(java.io.Reader) load(Reader)} / + * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)} + * methods load and store properties from and to a character based stream + * in a simple line-oriented format specified below. + * + * The {@link #load(java.io.InputStream) load(InputStream)} / + * {@link #store(java.io.OutputStream, java.lang.String) store(OutputStream, String)} + * methods work the same way as the load(Reader)/store(Writer, String) pair, except + * the input/output stream is encoded in ISO 8859-1 character encoding. + * Characters that cannot be directly represented in this encoding can be written using + * Unicode escapes as defined in section 3.3 of + * The Java™ Language Specification; + * only a single 'u' character is allowed in an escape + * sequence. The native2ascii tool can be used to convert property files to and + * from other character encodings. + * + *

The {@link #loadFromXML(InputStream)} and {@link + * #storeToXML(OutputStream, String, String)} methods load and store properties + * in a simple XML format. By default the UTF-8 character encoding is used, + * however a specific encoding may be specified if required. An XML properties + * document has the following DOCTYPE declaration: + * + *

+ * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+ * 
+ * Note that the system URI (http://java.sun.com/dtd/properties.dtd) is + * not accessed when exporting or importing properties; it merely + * serves as a string to uniquely identify the DTD, which is: + *
+ *    <?xml version="1.0" encoding="UTF-8"?>
+ *
+ *    <!-- DTD for properties -->
+ *
+ *    <!ELEMENT properties ( comment?, entry* ) >
+ *
+ *    <!ATTLIST properties version CDATA #FIXED "1.0">
+ *
+ *    <!ELEMENT comment (#PCDATA) >
+ *
+ *    <!ELEMENT entry (#PCDATA) >
+ *
+ *    <!ATTLIST entry key CDATA #REQUIRED>
+ * 
+ * + *

This class is thread-safe: multiple threads can share a single + * Properties object without the need for external synchronization. + * + * @see native2ascii tool for Solaris + * @see native2ascii tool for Windows + * + * @author Arthur van Hoff + * @author Michael McCloskey + * @author Xueming Shen + * @since JDK1.0 + */ +public +class Properties extends Hashtable { + /** + * use serialVersionUID from JDK 1.1.X for interoperability + */ + private static final long serialVersionUID = 4112578634029874840L; + + /** + * A property list that contains default values for any keys not + * found in this property list. + * + * @serial + */ + protected Properties defaults; + + /** + * Creates an empty property list with no default values. + */ + public Properties() { + this(null); + } + + /** + * Creates an empty property list with the specified defaults. + * + * @param defaults the defaults. + */ + public Properties(Properties defaults) { + this.defaults = defaults; + } + + /** + * Calls the Hashtable method put. Provided for + * parallelism with the getProperty method. Enforces use of + * strings for property keys and values. The value returned is the + * result of the Hashtable call to put. + * + * @param key the key to be placed into this property list. + * @param value the value corresponding to key. + * @return the previous value of the specified key in this property + * list, or null if it did not have one. + * @see #getProperty + * @since 1.2 + */ + public synchronized Object setProperty(String key, String value) { + return put(key, value); + } + + + /** + * Reads a property list (key and element pairs) from the input + * character stream in a simple line-oriented format. + *

+ * Properties are processed in terms of lines. There are two + * kinds of line, natural lines and logical lines. + * A natural line is defined as a line of + * characters that is terminated either by a set of line terminator + * characters (\n or \r or \r\n) + * or by the end of the stream. A natural line may be either a blank line, + * a comment line, or hold all or some of a key-element pair. A logical + * line holds all the data of a key-element pair, which may be spread + * out across several adjacent natural lines by escaping + * the line terminator sequence with a backslash character + * \. Note that a comment line cannot be extended + * in this manner; every natural line that is a comment must have + * its own comment indicator, as described below. Lines are read from + * input until the end of the stream is reached. + * + *

+ * A natural line that contains only white space characters is + * considered blank and is ignored. A comment line has an ASCII + * '#' or '!' as its first non-white + * space character; comment lines are also ignored and do not + * encode key-element information. In addition to line + * terminators, this format considers the characters space + * (' ', '\u0020'), tab + * ('\t', '\u0009'), and form feed + * ('\f', '\u000C') to be white + * space. + * + *

+ * If a logical line is spread across several natural lines, the + * backslash escaping the line terminator sequence, the line + * terminator sequence, and any white space at the start of the + * following line have no affect on the key or element values. + * The remainder of the discussion of key and element parsing + * (when loading) will assume all the characters constituting + * the key and element appear on a single natural line after + * line continuation characters have been removed. Note that + * it is not sufficient to only examine the character + * preceding a line terminator sequence to decide if the line + * terminator is escaped; there must be an odd number of + * contiguous backslashes for the line terminator to be escaped. + * Since the input is processed from left to right, a + * non-zero even number of 2n contiguous backslashes + * before a line terminator (or elsewhere) encodes n + * backslashes after escape processing. + * + *

+ * The key contains all of the characters in the line starting + * with the first non-white space character and up to, but not + * including, the first unescaped '=', + * ':', or white space character other than a line + * terminator. All of these key termination characters may be + * included in the key by escaping them with a preceding backslash + * character; for example,

+ * + * \:\=

+ * + * would be the two-character key ":=". Line + * terminator characters can be included using \r and + * \n escape sequences. Any white space after the + * key is skipped; if the first non-white space character after + * the key is '=' or ':', then it is + * ignored and any white space characters after it are also + * skipped. All remaining characters on the line become part of + * the associated element string; if there are no remaining + * characters, the element is the empty string + * "". Once the raw character sequences + * constituting the key and element are identified, escape + * processing is performed as described above. + * + *

+ * As an example, each of the following three lines specifies the key + * "Truth" and the associated element value + * "Beauty": + *

+ *

+     * Truth = Beauty
+     *  Truth:Beauty
+     * Truth                    :Beauty
+     * 
+ * As another example, the following three lines specify a single + * property: + *

+ *

+     * fruits                           apple, banana, pear, \
+     *                                  cantaloupe, watermelon, \
+     *                                  kiwi, mango
+     * 
+ * The key is "fruits" and the associated element is: + *

+ *

"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"
+ * Note that a space appears before each \ so that a space + * will appear after each comma in the final result; the \, + * line terminator, and leading white space on the continuation line are + * merely discarded and are not replaced by one or more other + * characters. + *

+ * As a third example, the line: + *

+ *

cheeses
+     * 
+ * specifies that the key is "cheeses" and the associated + * element is the empty string "".

+ *

+ * + * + * Characters in keys and elements can be represented in escape + * sequences similar to those used for character and string literals + * (see sections 3.3 and 3.10.6 of + * The Java™ Language Specification). + * + * The differences from the character escape sequences and Unicode + * escapes used for characters and strings are: + * + *

    + *
  • Octal escapes are not recognized. + * + *
  • The character sequence \b does not + * represent a backspace character. + * + *
  • The method does not treat a backslash character, + * \, before a non-valid escape character as an + * error; the backslash is silently dropped. For example, in a + * Java string the sequence "\z" would cause a + * compile time error. In contrast, this method silently drops + * the backslash. Therefore, this method treats the two character + * sequence "\b" as equivalent to the single + * character 'b'. + * + *
  • Escapes are not necessary for single and double quotes; + * however, by the rule above, single and double quote characters + * preceded by a backslash still yield single and double quote + * characters, respectively. + * + *
  • Only a single 'u' character is allowed in a Uniocde escape + * sequence. + * + *
+ *

+ * The specified stream remains open after this method returns. + * + * @param reader the input character stream. + * @throws IOException if an error occurred when reading from the + * input stream. + * @throws IllegalArgumentException if a malformed Unicode escape + * appears in the input. + * @since 1.6 + */ + public synchronized void load(Reader reader) throws IOException { + load0(new LineReader(reader)); + } + + /** + * Reads a property list (key and element pairs) from the input + * byte stream. The input stream is in a simple line-oriented + * format as specified in + * {@link #load(java.io.Reader) load(Reader)} and is assumed to use + * the ISO 8859-1 character encoding; that is each byte is one Latin1 + * character. Characters not in Latin1, and certain special characters, + * are represented in keys and elements using Unicode escapes as defined in + * section 3.3 of + * The Java™ Language Specification. + *

+ * The specified stream remains open after this method returns. + * + * @param inStream the input stream. + * @exception IOException if an error occurred when reading from the + * input stream. + * @throws IllegalArgumentException if the input stream contains a + * malformed Unicode escape sequence. + * @since 1.2 + */ + public synchronized void load(InputStream inStream) throws IOException { + load0(new LineReader(inStream)); + } + + private void load0 (LineReader lr) throws IOException { + char[] convtBuf = new char[1024]; + int limit; + int keyLen; + int valueStart; + char c; + boolean hasSep; + boolean precedingBackslash; + + while ((limit = lr.readLine()) >= 0) { + c = 0; + keyLen = 0; + valueStart = limit; + hasSep = false; + + //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); + precedingBackslash = false; + while (keyLen < limit) { + c = lr.lineBuf[keyLen]; + //need check if escaped. + if ((c == '=' || c == ':') && !precedingBackslash) { + valueStart = keyLen + 1; + hasSep = true; + break; + } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { + valueStart = keyLen + 1; + break; + } + if (c == '\\') { + precedingBackslash = !precedingBackslash; + } else { + precedingBackslash = false; + } + keyLen++; + } + while (valueStart < limit) { + c = lr.lineBuf[valueStart]; + if (c != ' ' && c != '\t' && c != '\f') { + if (!hasSep && (c == '=' || c == ':')) { + hasSep = true; + } else { + break; + } + } + valueStart++; + } + String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); + String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); + put(key, value); + } + } + + /* Read in a "logical line" from an InputStream/Reader, skip all comment + * and blank lines and filter out those leading whitespace characters + * (\u0020, \u0009 and \u000c) from the beginning of a "natural line". + * Method returns the char length of the "logical line" and stores + * the line in "lineBuf". + */ + class LineReader { + public LineReader(InputStream inStream) { + this.inStream = inStream; + inByteBuf = new byte[8192]; + } + + public LineReader(Reader reader) { + this.reader = reader; + inCharBuf = new char[8192]; + } + + byte[] inByteBuf; + char[] inCharBuf; + char[] lineBuf = new char[1024]; + int inLimit = 0; + int inOff = 0; + InputStream inStream; + Reader reader; + + int readLine() throws IOException { + int len = 0; + char c = 0; + + boolean skipWhiteSpace = true; + boolean isCommentLine = false; + boolean isNewLine = true; + boolean appendedLineBegin = false; + boolean precedingBackslash = false; + boolean skipLF = false; + + while (true) { + if (inOff >= inLimit) { + inLimit = (inStream==null)?reader.read(inCharBuf) + :inStream.read(inByteBuf); + inOff = 0; + if (inLimit <= 0) { + if (len == 0 || isCommentLine) { + return -1; + } + return len; + } + } + if (inStream != null) { + //The line below is equivalent to calling a + //ISO8859-1 decoder. + c = (char) (0xff & inByteBuf[inOff++]); + } else { + c = inCharBuf[inOff++]; + } + if (skipLF) { + skipLF = false; + if (c == '\n') { + continue; + } + } + if (skipWhiteSpace) { + if (c == ' ' || c == '\t' || c == '\f') { + continue; + } + if (!appendedLineBegin && (c == '\r' || c == '\n')) { + continue; + } + skipWhiteSpace = false; + appendedLineBegin = false; + } + if (isNewLine) { + isNewLine = false; + if (c == '#' || c == '!') { + isCommentLine = true; + continue; + } + } + + if (c != '\n' && c != '\r') { + lineBuf[len++] = c; + if (len == lineBuf.length) { + int newLength = lineBuf.length * 2; + if (newLength < 0) { + newLength = Integer.MAX_VALUE; + } + char[] buf = new char[newLength]; + System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length); + lineBuf = buf; + } + //flip the preceding backslash flag + if (c == '\\') { + precedingBackslash = !precedingBackslash; + } else { + precedingBackslash = false; + } + } + else { + // reached EOL + if (isCommentLine || len == 0) { + isCommentLine = false; + isNewLine = true; + skipWhiteSpace = true; + len = 0; + continue; + } + if (inOff >= inLimit) { + inLimit = (inStream==null) + ?reader.read(inCharBuf) + :inStream.read(inByteBuf); + inOff = 0; + if (inLimit <= 0) { + return len; + } + } + if (precedingBackslash) { + len -= 1; + //skip the leading whitespace characters in following line + skipWhiteSpace = true; + appendedLineBegin = true; + precedingBackslash = false; + if (c == '\r') { + skipLF = true; + } + } else { + return len; + } + } + } + } + } + + /* + * Converts encoded \uxxxx to unicode chars + * and changes special saved chars to their original forms + */ + private String loadConvert (char[] in, int off, int len, char[] convtBuf) { + if (convtBuf.length < len) { + int newLen = len * 2; + if (newLen < 0) { + newLen = Integer.MAX_VALUE; + } + convtBuf = new char[newLen]; + } + char aChar; + char[] out = convtBuf; + int outLen = 0; + int end = off + len; + + while (off < end) { + aChar = in[off++]; + if (aChar == '\\') { + aChar = in[off++]; + if(aChar == 'u') { + // Read the xxxx + int value=0; + for (int i=0; i<4; i++) { + aChar = in[off++]; + switch (aChar) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + value = (value << 4) + aChar - '0'; + break; + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + value = (value << 4) + 10 + aChar - 'a'; + break; + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + value = (value << 4) + 10 + aChar - 'A'; + break; + default: + throw new IllegalArgumentException( + "Malformed \\uxxxx encoding."); + } + } + out[outLen++] = (char)value; + } else { + if (aChar == 't') aChar = '\t'; + else if (aChar == 'r') aChar = '\r'; + else if (aChar == 'n') aChar = '\n'; + else if (aChar == 'f') aChar = '\f'; + out[outLen++] = aChar; + } + } else { + out[outLen++] = aChar; + } + } + return new String (out, 0, outLen); + } + + /* + * Converts unicodes to encoded \uxxxx and escapes + * special characters with a preceding slash + */ + private String saveConvert(String theString, + boolean escapeSpace, + boolean escapeUnicode) { + int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuffer outBuffer = new StringBuffer(bufLen); + + for(int x=0; x 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch(aChar) { + case ' ': + if (x == 0 || escapeSpace) + outBuffer.append('\\'); + outBuffer.append(' '); + break; + case '\t':outBuffer.append('\\'); outBuffer.append('t'); + break; + case '\n':outBuffer.append('\\'); outBuffer.append('n'); + break; + case '\r':outBuffer.append('\\'); outBuffer.append('r'); + break; + case '\f':outBuffer.append('\\'); outBuffer.append('f'); + break; + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) { + outBuffer.append('\\'); + outBuffer.append('u'); + outBuffer.append(toHex((aChar >> 12) & 0xF)); + outBuffer.append(toHex((aChar >> 8) & 0xF)); + outBuffer.append(toHex((aChar >> 4) & 0xF)); + outBuffer.append(toHex( aChar & 0xF)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } + + private static void writeComments(BufferedWriter bw, String comments) + throws IOException { + bw.write("#"); + int len = comments.length(); + int current = 0; + int last = 0; + char[] uu = new char[6]; + uu[0] = '\\'; + uu[1] = 'u'; + while (current < len) { + char c = comments.charAt(current); + if (c > '\u00ff' || c == '\n' || c == '\r') { + if (last != current) + bw.write(comments.substring(last, current)); + if (c > '\u00ff') { + uu[2] = toHex((c >> 12) & 0xf); + uu[3] = toHex((c >> 8) & 0xf); + uu[4] = toHex((c >> 4) & 0xf); + uu[5] = toHex( c & 0xf); + bw.write(new String(uu)); + } else { + bw.newLine(); + if (c == '\r' && + current != len - 1 && + comments.charAt(current + 1) == '\n') { + current++; + } + if (current == len - 1 || + (comments.charAt(current + 1) != '#' && + comments.charAt(current + 1) != '!')) + bw.write("#"); + } + last = current + 1; + } + current++; + } + if (last != current) + bw.write(comments.substring(last, current)); + bw.newLine(); + } + + /** + * Calls the store(OutputStream out, String comments) method + * and suppresses IOExceptions that were thrown. + * + * @deprecated This method does not throw an IOException if an I/O error + * occurs while saving the property list. The preferred way to save a + * properties list is via the store(OutputStream out, + * String comments) method or the + * storeToXML(OutputStream os, String comment) method. + * + * @param out an output stream. + * @param comments a description of the property list. + * @exception ClassCastException if this Properties object + * contains any keys or values that are not + * Strings. + */ + @Deprecated + public void save(OutputStream out, String comments) { + try { + store(out, comments); + } catch (IOException e) { + } + } + + /** + * Writes this property list (key and element pairs) in this + * Properties table to the output character stream in a + * format suitable for using the {@link #load(java.io.Reader) load(Reader)} + * method. + *

+ * Properties from the defaults table of this Properties + * table (if any) are not written out by this method. + *

+ * If the comments argument is not null, then an ASCII # + * character, the comments string, and a line separator are first written + * to the output stream. Thus, the comments can serve as an + * identifying comment. Any one of a line feed ('\n'), a carriage + * return ('\r'), or a carriage return followed immediately by a line feed + * in comments is replaced by a line separator generated by the Writer + * and if the next character in comments is not character # or + * character ! then an ASCII # is written out + * after that line separator. + *

+ * Next, a comment line is always written, consisting of an ASCII + * # character, the current date and time (as if produced + * by the toString method of Date for the + * current time), and a line separator as generated by the Writer. + *

+ * Then every entry in this Properties table is + * written out, one per line. For each entry the key string is + * written, then an ASCII =, then the associated + * element string. For the key, all space characters are + * written with a preceding \ character. For the + * element, leading space characters, but not embedded or trailing + * space characters, are written with a preceding \ + * character. The key and element characters #, + * !, =, and : are written + * with a preceding backslash to ensure that they are properly loaded. + *

+ * After the entries have been written, the output stream is flushed. + * The output stream remains open after this method returns. + *

+ * + * @param writer an output character stream writer. + * @param comments a description of the property list. + * @exception IOException if writing this property list to the specified + * output stream throws an IOException. + * @exception ClassCastException if this Properties object + * contains any keys or values that are not Strings. + * @exception NullPointerException if writer is null. + * @since 1.6 + */ + public void store(Writer writer, String comments) + throws IOException + { + store0((writer instanceof BufferedWriter)?(BufferedWriter)writer + : new BufferedWriter(writer), + comments, + false); + } + + /** + * Writes this property list (key and element pairs) in this + * Properties table to the output stream in a format suitable + * for loading into a Properties table using the + * {@link #load(InputStream) load(InputStream)} method. + *

+ * Properties from the defaults table of this Properties + * table (if any) are not written out by this method. + *

+ * This method outputs the comments, properties keys and values in + * the same format as specified in + * {@link #store(java.io.Writer, java.lang.String) store(Writer)}, + * with the following differences: + *

    + *
  • The stream is written using the ISO 8859-1 character encoding. + * + *
  • Characters not in Latin-1 in the comments are written as + * \uxxxx for their appropriate unicode + * hexadecimal value xxxx. + * + *
  • Characters less than \u0020 and characters greater + * than \u007E in property keys or values are written + * as \uxxxx for the appropriate hexadecimal + * value xxxx. + *
+ *

+ * After the entries have been written, the output stream is flushed. + * The output stream remains open after this method returns. + *

+ * @param out an output stream. + * @param comments a description of the property list. + * @exception IOException if writing this property list to the specified + * output stream throws an IOException. + * @exception ClassCastException if this Properties object + * contains any keys or values that are not Strings. + * @exception NullPointerException if out is null. + * @since 1.2 + */ + public void store(OutputStream out, String comments) + throws IOException + { + store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")), + comments, + true); + } + + private void store0(BufferedWriter bw, String comments, boolean escUnicode) + throws IOException + { + if (comments != null) { + writeComments(bw, comments); + } + bw.write("#" + new Date().toString()); + bw.newLine(); + synchronized (this) { + for (Enumeration e = keys(); e.hasMoreElements();) { + String key = (String)e.nextElement(); + String val = (String)get(key); + key = saveConvert(key, true, escUnicode); + /* No need to escape embedded and trailing spaces for value, hence + * pass false to flag. + */ + val = saveConvert(val, false, escUnicode); + bw.write(key + "=" + val); + bw.newLine(); + } + } + bw.flush(); + } + + /** + * Loads all of the properties represented by the XML document on the + * specified input stream into this properties table. + * + *

The XML document must have the following DOCTYPE declaration: + *

+     * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+     * 
+ * Furthermore, the document must satisfy the properties DTD described + * above. + * + *

The specified stream is closed after this method returns. + * + * @param in the input stream from which to read the XML document. + * @throws IOException if reading from the specified input stream + * results in an IOException. + * @throws InvalidPropertiesFormatException Data on input stream does not + * constitute a valid XML document with the mandated document type. + * @throws NullPointerException if in is null. + * @see #storeToXML(OutputStream, String, String) + * @since 1.5 + */ + public synchronized void loadFromXML(InputStream in) + throws IOException, InvalidPropertiesFormatException + { + if (in == null) + throw new NullPointerException(); + XMLUtils.load(this, in); + in.close(); + } + + /** + * Emits an XML document representing all of the properties contained + * in this table. + * + *

An invocation of this method of the form props.storeToXML(os, + * comment) behaves in exactly the same way as the invocation + * props.storeToXML(os, comment, "UTF-8");. + * + * @param os the output stream on which to emit the XML document. + * @param comment a description of the property list, or null + * if no comment is desired. + * @throws IOException if writing to the specified output stream + * results in an IOException. + * @throws NullPointerException if os is null. + * @throws ClassCastException if this Properties object + * contains any keys or values that are not + * Strings. + * @see #loadFromXML(InputStream) + * @since 1.5 + */ + public void storeToXML(OutputStream os, String comment) + throws IOException + { + if (os == null) + throw new NullPointerException(); + storeToXML(os, comment, "UTF-8"); + } + + /** + * Emits an XML document representing all of the properties contained + * in this table, using the specified encoding. + * + *

The XML document will have the following DOCTYPE declaration: + *

+     * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+     * 
+ * + *

If the specified comment is null then no comment + * will be stored in the document. + * + *

The specified stream remains open after this method returns. + * + * @param os the output stream on which to emit the XML document. + * @param comment a description of the property list, or null + * if no comment is desired. + * @param encoding the name of a supported + * + * character encoding + * + * @throws IOException if writing to the specified output stream + * results in an IOException. + * @throws NullPointerException if os is null, + * or if encoding is null. + * @throws ClassCastException if this Properties object + * contains any keys or values that are not + * Strings. + * @see #loadFromXML(InputStream) + * @since 1.5 + */ + public void storeToXML(OutputStream os, String comment, String encoding) + throws IOException + { + if (os == null) + throw new NullPointerException(); + XMLUtils.save(this, os, comment, encoding); + } + + /** + * Searches for the property with the specified key in this property list. + * If the key is not found in this property list, the default property list, + * and its defaults, recursively, are then checked. The method returns + * null if the property is not found. + * + * @param key the property key. + * @return the value in this property list with the specified key value. + * @see #setProperty + * @see #defaults + */ + public String getProperty(String key) { + Object oval = super.get(key); + String sval = (oval instanceof String) ? (String)oval : null; + return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval; + } + + /** + * Searches for the property with the specified key in this property list. + * If the key is not found in this property list, the default property list, + * and its defaults, recursively, are then checked. The method returns the + * default value argument if the property is not found. + * + * @param key the hashtable key. + * @param defaultValue a default value. + * + * @return the value in this property list with the specified key value. + * @see #setProperty + * @see #defaults + */ + public String getProperty(String key, String defaultValue) { + String val = getProperty(key); + return (val == null) ? defaultValue : val; + } + + /** + * Returns an enumeration of all the keys in this property list, + * including distinct keys in the default property list if a key + * of the same name has not already been found from the main + * properties list. + * + * @return an enumeration of all the keys in this property list, including + * the keys in the default property list. + * @throws ClassCastException if any key in this property list + * is not a string. + * @see java.util.Enumeration + * @see java.util.Properties#defaults + * @see #stringPropertyNames + */ + public Enumeration propertyNames() { + Hashtable h = new Hashtable(); + enumerate(h); + return h.keys(); + } + + /** + * Returns a set of keys in this property list where + * the key and its corresponding value are strings, + * including distinct keys in the default property list if a key + * of the same name has not already been found from the main + * properties list. Properties whose key or value is not + * of type String are omitted. + *

+ * The returned set is not backed by the Properties object. + * Changes to this Properties are not reflected in the set, + * or vice versa. + * + * @return a set of keys in this property list where + * the key and its corresponding value are strings, + * including the keys in the default property list. + * @see java.util.Properties#defaults + * @since 1.6 + */ + public Set stringPropertyNames() { + Hashtable h = new Hashtable<>(); + enumerateStringProperties(h); + return h.keySet(); + } + + /** + * Prints this property list out to the specified output stream. + * This method is useful for debugging. + * + * @param out an output stream. + * @throws ClassCastException if any key in this property list + * is not a string. + */ + public void list(PrintStream out) { + out.println("-- listing properties --"); + Hashtable h = new Hashtable(); + enumerate(h); + for (Enumeration e = h.keys() ; e.hasMoreElements() ;) { + String key = (String)e.nextElement(); + String val = (String)h.get(key); + if (val.length() > 40) { + val = val.substring(0, 37) + "..."; + } + out.println(key + "=" + val); + } + } + + /** + * Prints this property list out to the specified output stream. + * This method is useful for debugging. + * + * @param out an output stream. + * @throws ClassCastException if any key in this property list + * is not a string. + * @since JDK1.1 + */ + /* + * Rather than use an anonymous inner class to share common code, this + * method is duplicated in order to ensure that a non-1.1 compiler can + * compile this file. + */ + public void list(PrintWriter out) { + out.println("-- listing properties --"); + Hashtable h = new Hashtable(); + enumerate(h); + for (Enumeration e = h.keys() ; e.hasMoreElements() ;) { + String key = (String)e.nextElement(); + String val = (String)h.get(key); + if (val.length() > 40) { + val = val.substring(0, 37) + "..."; + } + out.println(key + "=" + val); + } + } + + /** + * Enumerates all key/value pairs in the specified hashtable. + * @param h the hashtable + * @throws ClassCastException if any of the property keys + * is not of String type. + */ + private synchronized void enumerate(Hashtable h) { + if (defaults != null) { + defaults.enumerate(h); + } + for (Enumeration e = keys() ; e.hasMoreElements() ;) { + String key = (String)e.nextElement(); + h.put(key, get(key)); + } + } + + /** + * Enumerates all key/value pairs in the specified hashtable + * and omits the property if the key or value is not a string. + * @param h the hashtable + */ + private synchronized void enumerateStringProperties(Hashtable h) { + if (defaults != null) { + defaults.enumerateStringProperties(h); + } + for (Enumeration e = keys() ; e.hasMoreElements() ;) { + Object k = e.nextElement(); + Object v = get(k); + if (k instanceof String && v instanceof String) { + h.put((String) k, (String) v); + } + } + } + + /** + * Convert a nibble to a hex character + * @param nibble the nibble to convert. + */ + private static char toHex(int nibble) { + return hexDigit[(nibble & 0xF)]; + } + + /** A table of hex digits */ + private static final char[] hexDigit = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' + }; +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/PropertyResourceBundle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/PropertyResourceBundle.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,190 @@ +/* + * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + */ + +package java.util; + +import java.io.InputStream; +import java.io.Reader; +import java.io.IOException; +import sun.util.ResourceBundleEnumeration; + +/** + * PropertyResourceBundle is a concrete subclass of + * ResourceBundle that manages resources for a locale + * using a set of static strings from a property file. See + * {@link ResourceBundle ResourceBundle} for more information about resource + * bundles. + * + *

+ * Unlike other types of resource bundle, you don't subclass + * PropertyResourceBundle. Instead, you supply properties + * files containing the resource data. ResourceBundle.getBundle + * will automatically look for the appropriate properties file and create a + * PropertyResourceBundle that refers to it. See + * {@link ResourceBundle#getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader) ResourceBundle.getBundle} + * for a complete description of the search and instantiation strategy. + * + *

+ * The following example shows a member of a resource + * bundle family with the base name "MyResources". + * The text defines the bundle "MyResources_de", + * the German member of the bundle family. + * This member is based on PropertyResourceBundle, and the text + * therefore is the content of the file "MyResources_de.properties" + * (a related example shows + * how you can add bundles to this family that are implemented as subclasses + * of ListResourceBundle). + * The keys in this example are of the form "s1" etc. The actual + * keys are entirely up to your choice, so long as they are the same as + * the keys you use in your program to retrieve the objects from the bundle. + * Keys are case-sensitive. + *

+ *
+ * # MessageFormat pattern
+ * s1=Die Platte \"{1}\" enthält {0}.
+ *
+ * # location of {0} in pattern
+ * s2=1
+ *
+ * # sample disk name
+ * s3=Meine Platte
+ *
+ * # first ChoiceFormat choice
+ * s4=keine Dateien
+ *
+ * # second ChoiceFormat choice
+ * s5=eine Datei
+ *
+ * # third ChoiceFormat choice
+ * s6={0,number} Dateien
+ *
+ * # sample date
+ * s7=3. März 1996
+ * 
+ *
+ * + *

+ * Note: PropertyResourceBundle can be constructed either + * from an InputStream or a Reader, which represents a property file. + * Constructing a PropertyResourceBundle instance from an InputStream requires + * that the input stream be encoded in ISO-8859-1. In that case, characters + * that cannot be represented in ISO-8859-1 encoding must be represented by Unicode Escapes + * as defined in section 3.3 of + * The Java™ Language Specification + * whereas the other constructor which takes a Reader does not have that limitation. + * + * @see ResourceBundle + * @see ListResourceBundle + * @see Properties + * @since JDK1.1 + */ +public class PropertyResourceBundle extends ResourceBundle { + /** + * Creates a property resource bundle from an {@link java.io.InputStream + * InputStream}. The property file read with this constructor + * must be encoded in ISO-8859-1. + * + * @param stream an InputStream that represents a property file + * to read from. + * @throws IOException if an I/O error occurs + * @throws NullPointerException if stream is null + */ + public PropertyResourceBundle (InputStream stream) throws IOException { + Properties properties = new Properties(); + properties.load(stream); + lookup = new HashMap(properties); + } + + /** + * Creates a property resource bundle from a {@link java.io.Reader + * Reader}. Unlike the constructor + * {@link #PropertyResourceBundle(java.io.InputStream) PropertyResourceBundle(InputStream)}, + * there is no limitation as to the encoding of the input property file. + * + * @param reader a Reader that represents a property file to + * read from. + * @throws IOException if an I/O error occurs + * @throws NullPointerException if reader is null + * @since 1.6 + */ + public PropertyResourceBundle (Reader reader) throws IOException { + Properties properties = new Properties(); + properties.load(reader); + lookup = new HashMap(properties); + } + + // Implements java.util.ResourceBundle.handleGetObject; inherits javadoc specification. + public Object handleGetObject(String key) { + if (key == null) { + throw new NullPointerException(); + } + return lookup.get(key); + } + + /** + * Returns an Enumeration of the keys contained in + * this ResourceBundle and its parent bundles. + * + * @return an Enumeration of the keys contained in + * this ResourceBundle and its parent bundles. + * @see #keySet() + */ + public Enumeration getKeys() { + ResourceBundle parent = this.parent; + return new ResourceBundleEnumeration(lookup.keySet(), + (parent != null) ? parent.getKeys() : null); + } + + /** + * Returns a Set of the keys contained + * only in this ResourceBundle. + * + * @return a Set of the keys contained only in this + * ResourceBundle + * @since 1.6 + * @see #keySet() + */ + protected Set handleKeySet() { + return lookup.keySet(); + } + + // ==================privates==================== + + private Map lookup; +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/ResourceBundle.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/ResourceBundle.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,2911 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved + * + * The original version of this source code and documentation + * is copyrighted and owned by Taligent, Inc., a wholly-owned + * subsidiary of IBM. These materials are provided under terms + * of a License Agreement between Taligent and Sun. This technology + * is protected by multiple US and International patents. + * + * This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.jar.JarEntry; + +import sun.util.locale.BaseLocale; +import sun.util.locale.LocaleObjectCache; + + +/** + * + * Resource bundles contain locale-specific objects. When your program needs a + * locale-specific resource, a String for example, your program can + * load it from the resource bundle that is appropriate for the current user's + * locale. In this way, you can write program code that is largely independent + * of the user's locale isolating most, if not all, of the locale-specific + * information in resource bundles. + * + *

+ * This allows you to write programs that can: + *

    + *
  • be easily localized, or translated, into different languages + *
  • handle multiple locales at once + *
  • be easily modified later to support even more locales + *
+ * + *

+ * Resource bundles belong to families whose members share a common base + * name, but whose names also have additional components that identify + * their locales. For example, the base name of a family of resource + * bundles might be "MyResources". The family should have a default + * resource bundle which simply has the same name as its family - + * "MyResources" - and will be used as the bundle of last resort if a + * specific locale is not supported. The family can then provide as + * many locale-specific members as needed, for example a German one + * named "MyResources_de". + * + *

+ * Each resource bundle in a family contains the same items, but the items have + * been translated for the locale represented by that resource bundle. + * For example, both "MyResources" and "MyResources_de" may have a + * String that's used on a button for canceling operations. + * In "MyResources" the String may contain "Cancel" and in + * "MyResources_de" it may contain "Abbrechen". + * + *

+ * If there are different resources for different countries, you + * can make specializations: for example, "MyResources_de_CH" contains objects for + * the German language (de) in Switzerland (CH). If you want to only + * modify some of the resources + * in the specialization, you can do so. + * + *

+ * When your program needs a locale-specific object, it loads + * the ResourceBundle class using the + * {@link #getBundle(java.lang.String, java.util.Locale) getBundle} + * method: + *

+ *
+ * ResourceBundle myResources =
+ *      ResourceBundle.getBundle("MyResources", currentLocale);
+ * 
+ *
+ * + *

+ * Resource bundles contain key/value pairs. The keys uniquely + * identify a locale-specific object in the bundle. Here's an + * example of a ListResourceBundle that contains + * two key/value pairs: + *

+ *
+ * public class MyResources extends ListResourceBundle {
+ *     protected Object[][] getContents() {
+ *         return new Object[][] {
+ *             // LOCALIZE THE SECOND STRING OF EACH ARRAY (e.g., "OK")
+ *             {"OkKey", "OK"},
+ *             {"CancelKey", "Cancel"},
+ *             // END OF MATERIAL TO LOCALIZE
+ *        };
+ *     }
+ * }
+ * 
+ *
+ * Keys are always Strings. + * In this example, the keys are "OkKey" and "CancelKey". + * In the above example, the values + * are also Strings--"OK" and "Cancel"--but + * they don't have to be. The values can be any type of object. + * + *

+ * You retrieve an object from resource bundle using the appropriate + * getter method. Because "OkKey" and "CancelKey" + * are both strings, you would use getString to retrieve them: + *

+ *
+ * button1 = new Button(myResources.getString("OkKey"));
+ * button2 = new Button(myResources.getString("CancelKey"));
+ * 
+ *
+ * The getter methods all require the key as an argument and return + * the object if found. If the object is not found, the getter method + * throws a MissingResourceException. + * + *

+ * Besides getString, ResourceBundle also provides + * a method for getting string arrays, getStringArray, + * as well as a generic getObject method for any other + * type of object. When using getObject, you'll + * have to cast the result to the appropriate type. For example: + *

+ *
+ * int[] myIntegers = (int[]) myResources.getObject("intList");
+ * 
+ *
+ * + *

+ * The Java Platform provides two subclasses of ResourceBundle, + * ListResourceBundle and PropertyResourceBundle, + * that provide a fairly simple way to create resources. + * As you saw briefly in a previous example, ListResourceBundle + * manages its resource as a list of key/value pairs. + * PropertyResourceBundle uses a properties file to manage + * its resources. + * + *

+ * If ListResourceBundle or PropertyResourceBundle + * do not suit your needs, you can write your own ResourceBundle + * subclass. Your subclasses must override two methods: handleGetObject + * and getKeys(). + * + *

ResourceBundle.Control

+ * + * The {@link ResourceBundle.Control} class provides information necessary + * to perform the bundle loading process by the getBundle + * factory methods that take a ResourceBundle.Control + * instance. You can implement your own subclass in order to enable + * non-standard resource bundle formats, change the search strategy, or + * define caching parameters. Refer to the descriptions of the class and the + * {@link #getBundle(String, Locale, ClassLoader, Control) getBundle} + * factory method for details. + * + *

Cache Management

+ * + * Resource bundle instances created by the getBundle factory + * methods are cached by default, and the factory methods return the same + * resource bundle instance multiple times if it has been + * cached. getBundle clients may clear the cache, manage the + * lifetime of cached resource bundle instances using time-to-live values, + * or specify not to cache resource bundle instances. Refer to the + * descriptions of the {@linkplain #getBundle(String, Locale, ClassLoader, + * Control) getBundle factory method}, {@link + * #clearCache(ClassLoader) clearCache}, {@link + * Control#getTimeToLive(String, Locale) + * ResourceBundle.Control.getTimeToLive}, and {@link + * Control#needsReload(String, Locale, String, ClassLoader, ResourceBundle, + * long) ResourceBundle.Control.needsReload} for details. + * + *

Example

+ * + * The following is a very simple example of a ResourceBundle + * subclass, MyResources, that manages two resources (for a larger number of + * resources you would probably use a Map). + * Notice that you don't need to supply a value if + * a "parent-level" ResourceBundle handles the same + * key with the same value (as for the okKey below). + *
+ *
+ * // default (English language, United States)
+ * public class MyResources extends ResourceBundle {
+ *     public Object handleGetObject(String key) {
+ *         if (key.equals("okKey")) return "Ok";
+ *         if (key.equals("cancelKey")) return "Cancel";
+ *         return null;
+ *     }
+ *
+ *     public Enumeration<String> getKeys() {
+ *         return Collections.enumeration(keySet());
+ *     }
+ *
+ *     // Overrides handleKeySet() so that the getKeys() implementation
+ *     // can rely on the keySet() value.
+ *     protected Set<String> handleKeySet() {
+ *         return new HashSet<String>(Arrays.asList("okKey", "cancelKey"));
+ *     }
+ * }
+ *
+ * // German language
+ * public class MyResources_de extends MyResources {
+ *     public Object handleGetObject(String key) {
+ *         // don't need okKey, since parent level handles it.
+ *         if (key.equals("cancelKey")) return "Abbrechen";
+ *         return null;
+ *     }
+ *
+ *     protected Set<String> handleKeySet() {
+ *         return new HashSet<String>(Arrays.asList("cancelKey"));
+ *     }
+ * }
+ * 
+ *
+ * You do not have to restrict yourself to using a single family of + * ResourceBundles. For example, you could have a set of bundles for + * exception messages, ExceptionResources + * (ExceptionResources_fr, ExceptionResources_de, ...), + * and one for widgets, WidgetResource (WidgetResources_fr, + * WidgetResources_de, ...); breaking up the resources however you like. + * + * @see ListResourceBundle + * @see PropertyResourceBundle + * @see MissingResourceException + * @since JDK1.1 + */ +public abstract class ResourceBundle { + + /** initial size of the bundle cache */ + private static final int INITIAL_CACHE_SIZE = 32; + + /** constant indicating that no resource bundle exists */ + private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() { + public Enumeration getKeys() { return null; } + protected Object handleGetObject(String key) { return null; } + public String toString() { return "NONEXISTENT_BUNDLE"; } + }; + + + /** + * The cache is a map from cache keys (with bundle base name, locale, and + * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a + * BundleReference. + * + * The cache is a ConcurrentMap, allowing the cache to be searched + * concurrently by multiple threads. This will also allow the cache keys + * to be reclaimed along with the ClassLoaders they reference. + * + * This variable would be better named "cache", but we keep the old + * name for compatibility with some workarounds for bug 4212439. + */ + private static final ConcurrentMap cacheList + = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE); + + /** + * Queue for reference objects referring to class loaders or bundles. + */ + private static final ReferenceQueue referenceQueue = new ReferenceQueue(); + + /** + * The parent bundle of this bundle. + * The parent bundle is searched by {@link #getObject getObject} + * when this bundle does not contain a particular resource. + */ + protected ResourceBundle parent = null; + + /** + * The locale for this bundle. + */ + private Locale locale = null; + + /** + * The base bundle name for this bundle. + */ + private String name; + + /** + * The flag indicating this bundle has expired in the cache. + */ + private volatile boolean expired; + + /** + * The back link to the cache key. null if this bundle isn't in + * the cache (yet) or has expired. + */ + private volatile CacheKey cacheKey; + + /** + * A Set of the keys contained only in this ResourceBundle. + */ + private volatile Set keySet; + + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + public ResourceBundle() { + } + + /** + * Gets a string for the given key from this resource bundle or one of its parents. + * Calling this method is equivalent to calling + *
+ * (String) {@link #getObject(java.lang.String) getObject}(key). + *
+ * + * @param key the key for the desired string + * @exception NullPointerException if key is null + * @exception MissingResourceException if no object for the given key can be found + * @exception ClassCastException if the object found for the given key is not a string + * @return the string for the given key + */ + public final String getString(String key) { + return (String) getObject(key); + } + + /** + * Gets a string array for the given key from this resource bundle or one of its parents. + * Calling this method is equivalent to calling + *
+ * (String[]) {@link #getObject(java.lang.String) getObject}(key). + *
+ * + * @param key the key for the desired string array + * @exception NullPointerException if key is null + * @exception MissingResourceException if no object for the given key can be found + * @exception ClassCastException if the object found for the given key is not a string array + * @return the string array for the given key + */ + public final String[] getStringArray(String key) { + return (String[]) getObject(key); + } + + /** + * Gets an object for the given key from this resource bundle or one of its parents. + * This method first tries to obtain the object from this resource bundle using + * {@link #handleGetObject(java.lang.String) handleGetObject}. + * If not successful, and the parent resource bundle is not null, + * it calls the parent's getObject method. + * If still not successful, it throws a MissingResourceException. + * + * @param key the key for the desired object + * @exception NullPointerException if key is null + * @exception MissingResourceException if no object for the given key can be found + * @return the object for the given key + */ + public final Object getObject(String key) { + Object obj = handleGetObject(key); + if (obj == null) { + if (parent != null) { + obj = parent.getObject(key); + } + if (obj == null) + throw new MissingResourceException("Can't find resource for bundle " + +this.getClass().getName() + +", key "+key, + this.getClass().getName(), + key); + } + return obj; + } + + /** + * Returns the locale of this resource bundle. This method can be used after a + * call to getBundle() to determine whether the resource bundle returned really + * corresponds to the requested locale or is a fallback. + * + * @return the locale of this resource bundle + */ + public Locale getLocale() { + return locale; + } + + /* + * Automatic determination of the ClassLoader to be used to load + * resources on behalf of the client. N.B. The client is getLoader's + * caller's caller. + */ + private static ClassLoader getLoader() { + Class[] stack = getClassContext(); + /* Magic number 2 identifies our caller's caller */ + Class c = stack[2]; + ClassLoader cl = (c == null) ? null : c.getClassLoader(); + if (cl == null) { + // When the caller's loader is the boot class loader, cl is null + // here. In that case, ClassLoader.getSystemClassLoader() may + // return the same class loader that the application is + // using. We therefore use a wrapper ClassLoader to create a + // separate scope for bundles loaded on behalf of the Java + // runtime so that these bundles cannot be returned from the + // cache to the application (5048280). + cl = RBClassLoader.INSTANCE; + } + return cl; + } + + private static native Class[] getClassContext(); + + /** + * A wrapper of ClassLoader.getSystemClassLoader(). + */ + private static class RBClassLoader extends ClassLoader { + private static final RBClassLoader INSTANCE = AccessController.doPrivileged( + new PrivilegedAction() { + public RBClassLoader run() { + return new RBClassLoader(); + } + }); + private static final ClassLoader loader = ClassLoader.getSystemClassLoader(); + + private RBClassLoader() { + } + public Class loadClass(String name) throws ClassNotFoundException { + if (loader != null) { + return loader.loadClass(name); + } + return Class.forName(name); + } + public URL getResource(String name) { + if (loader != null) { + return loader.getResource(name); + } + return ClassLoader.getSystemResource(name); + } + public InputStream getResourceAsStream(String name) { + if (loader != null) { + return loader.getResourceAsStream(name); + } + return ClassLoader.getSystemResourceAsStream(name); + } + } + + /** + * Sets the parent bundle of this bundle. + * The parent bundle is searched by {@link #getObject getObject} + * when this bundle does not contain a particular resource. + * + * @param parent this bundle's parent bundle. + */ + protected void setParent(ResourceBundle parent) { + assert parent != NONEXISTENT_BUNDLE; + this.parent = parent; + } + + /** + * Key used for cached resource bundles. The key checks the base + * name, the locale, and the class loader to determine if the + * resource is a match to the requested one. The loader may be + * null, but the base name and the locale must have a non-null + * value. + */ + private static final class CacheKey implements Cloneable { + // These three are the actual keys for lookup in Map. + private String name; + private Locale locale; + private LoaderReference loaderRef; + + // bundle format which is necessary for calling + // Control.needsReload(). + private String format; + + // These time values are in CacheKey so that NONEXISTENT_BUNDLE + // doesn't need to be cloned for caching. + + // The time when the bundle has been loaded + private volatile long loadTime; + + // The time when the bundle expires in the cache, or either + // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL. + private volatile long expirationTime; + + // Placeholder for an error report by a Throwable + private Throwable cause; + + // Hash code value cache to avoid recalculating the hash code + // of this instance. + private int hashCodeCache; + + CacheKey(String baseName, Locale locale, ClassLoader loader) { + this.name = baseName; + this.locale = locale; + if (loader == null) { + this.loaderRef = null; + } else { + loaderRef = new LoaderReference(loader, referenceQueue, this); + } + calculateHashCode(); + } + + String getName() { + return name; + } + + CacheKey setName(String baseName) { + if (!this.name.equals(baseName)) { + this.name = baseName; + calculateHashCode(); + } + return this; + } + + Locale getLocale() { + return locale; + } + + CacheKey setLocale(Locale locale) { + if (!this.locale.equals(locale)) { + this.locale = locale; + calculateHashCode(); + } + return this; + } + + ClassLoader getLoader() { + return (loaderRef != null) ? loaderRef.get() : null; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + try { + final CacheKey otherEntry = (CacheKey)other; + //quick check to see if they are not equal + if (hashCodeCache != otherEntry.hashCodeCache) { + return false; + } + //are the names the same? + if (!name.equals(otherEntry.name)) { + return false; + } + // are the locales the same? + if (!locale.equals(otherEntry.locale)) { + return false; + } + //are refs (both non-null) or (both null)? + if (loaderRef == null) { + return otherEntry.loaderRef == null; + } + ClassLoader loader = loaderRef.get(); + return (otherEntry.loaderRef != null) + // with a null reference we can no longer find + // out which class loader was referenced; so + // treat it as unequal + && (loader != null) + && (loader == otherEntry.loaderRef.get()); + } catch (NullPointerException e) { + } catch (ClassCastException e) { + } + return false; + } + + public int hashCode() { + return hashCodeCache; + } + + private void calculateHashCode() { + hashCodeCache = name.hashCode() << 3; + hashCodeCache ^= locale.hashCode(); + ClassLoader loader = getLoader(); + if (loader != null) { + hashCodeCache ^= loader.hashCode(); + } + } + + public Object clone() { + try { + CacheKey clone = (CacheKey) super.clone(); + if (loaderRef != null) { + clone.loaderRef = new LoaderReference(loaderRef.get(), + referenceQueue, clone); + } + // Clear the reference to a Throwable + clone.cause = null; + return clone; + } catch (CloneNotSupportedException e) { + //this should never happen + throw new InternalError(); + } + } + + String getFormat() { + return format; + } + + void setFormat(String format) { + this.format = format; + } + + private void setCause(Throwable cause) { + if (this.cause == null) { + this.cause = cause; + } else { + // Override the cause if the previous one is + // ClassNotFoundException. + if (this.cause instanceof ClassNotFoundException) { + this.cause = cause; + } + } + } + + private Throwable getCause() { + return cause; + } + + public String toString() { + String l = locale.toString(); + if (l.length() == 0) { + if (locale.getVariant().length() != 0) { + l = "__" + locale.getVariant(); + } else { + l = "\"\""; + } + } + return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader() + + "(format=" + format + ")]"; + } + } + + /** + * The common interface to get a CacheKey in LoaderReference and + * BundleReference. + */ + private static interface CacheKeyReference { + public CacheKey getCacheKey(); + } + + /** + * References to class loaders are weak references, so that they can be + * garbage collected when nobody else is using them. The ResourceBundle + * class has no reason to keep class loaders alive. + */ + private static final class LoaderReference extends WeakReference + implements CacheKeyReference { + private CacheKey cacheKey; + + LoaderReference(ClassLoader referent, ReferenceQueue q, CacheKey key) { + super(referent, q); + cacheKey = key; + } + + public CacheKey getCacheKey() { + return cacheKey; + } + } + + /** + * References to bundles are soft references so that they can be garbage + * collected when they have no hard references. + */ + private static final class BundleReference extends SoftReference + implements CacheKeyReference { + private CacheKey cacheKey; + + BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) { + super(referent, q); + cacheKey = key; + } + + public CacheKey getCacheKey() { + return cacheKey; + } + } + + /** + * Gets a resource bundle using the specified base name, the default locale, + * and the caller's class loader. Calling this method is equivalent to calling + *
+ * getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader()), + *
+ * except that getClassLoader() is run with the security + * privileges of ResourceBundle. + * See {@link #getBundle(String, Locale, ClassLoader) getBundle} + * for a complete description of the search and instantiation strategy. + * + * @param baseName the base name of the resource bundle, a fully qualified class name + * @exception java.lang.NullPointerException + * if baseName is null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @return a resource bundle for the given base name and the default locale + */ + public static final ResourceBundle getBundle(String baseName) + { + return getBundleImpl(baseName, Locale.getDefault(), + /* must determine loader here, else we break stack invariant */ + getLoader(), + Control.INSTANCE); + } + + /** + * Returns a resource bundle using the specified base name, the + * default locale and the specified control. Calling this method + * is equivalent to calling + *
+     * getBundle(baseName, Locale.getDefault(),
+     *           this.getClass().getClassLoader(), control),
+     * 
+ * except that getClassLoader() is run with the security + * privileges of ResourceBundle. See {@link + * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the + * complete description of the resource bundle loading process with a + * ResourceBundle.Control. + * + * @param baseName + * the base name of the resource bundle, a fully qualified class + * name + * @param control + * the control which gives information for the resource bundle + * loading process + * @return a resource bundle for the given base name and the default + * locale + * @exception NullPointerException + * if baseName or control is + * null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @exception IllegalArgumentException + * if the given control doesn't perform properly + * (e.g., control.getCandidateLocales returns null.) + * Note that validation of control is performed as + * needed. + * @since 1.6 + */ + public static final ResourceBundle getBundle(String baseName, + Control control) { + return getBundleImpl(baseName, Locale.getDefault(), + /* must determine loader here, else we break stack invariant */ + getLoader(), + control); + } + + /** + * Gets a resource bundle using the specified base name and locale, + * and the caller's class loader. Calling this method is equivalent to calling + *
+ * getBundle(baseName, locale, this.getClass().getClassLoader()), + *
+ * except that getClassLoader() is run with the security + * privileges of ResourceBundle. + * See {@link #getBundle(String, Locale, ClassLoader) getBundle} + * for a complete description of the search and instantiation strategy. + * + * @param baseName + * the base name of the resource bundle, a fully qualified class name + * @param locale + * the locale for which a resource bundle is desired + * @exception NullPointerException + * if baseName or locale is null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @return a resource bundle for the given base name and locale + */ + public static final ResourceBundle getBundle(String baseName, + Locale locale) + { + return getBundleImpl(baseName, locale, + /* must determine loader here, else we break stack invariant */ + getLoader(), + Control.INSTANCE); + } + + /** + * Returns a resource bundle using the specified base name, target + * locale and control, and the caller's class loader. Calling this + * method is equivalent to calling + *
+     * getBundle(baseName, targetLocale, this.getClass().getClassLoader(),
+     *           control),
+     * 
+ * except that getClassLoader() is run with the security + * privileges of ResourceBundle. See {@link + * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the + * complete description of the resource bundle loading process with a + * ResourceBundle.Control. + * + * @param baseName + * the base name of the resource bundle, a fully qualified + * class name + * @param targetLocale + * the locale for which a resource bundle is desired + * @param control + * the control which gives information for the resource + * bundle loading process + * @return a resource bundle for the given base name and a + * Locale in locales + * @exception NullPointerException + * if baseName, locales or + * control is null + * @exception MissingResourceException + * if no resource bundle for the specified base name in any + * of the locales can be found. + * @exception IllegalArgumentException + * if the given control doesn't perform properly + * (e.g., control.getCandidateLocales returns null.) + * Note that validation of control is performed as + * needed. + * @since 1.6 + */ + public static final ResourceBundle getBundle(String baseName, Locale targetLocale, + Control control) { + return getBundleImpl(baseName, targetLocale, + /* must determine loader here, else we break stack invariant */ + getLoader(), + control); + } + + /** + * Gets a resource bundle using the specified base name, locale, and class + * loader. + * + *

This method behaves the same as calling + * {@link #getBundle(String, Locale, ClassLoader, Control)} passing a + * default instance of {@link Control}. The following describes this behavior. + * + *

getBundle uses the base name, the specified locale, and + * the default locale (obtained from {@link java.util.Locale#getDefault() + * Locale.getDefault}) to generate a sequence of candidate bundle names. If the specified + * locale's language, script, country, and variant are all empty strings, + * then the base name is the only candidate bundle name. Otherwise, a list + * of candidate locales is generated from the attribute values of the + * specified locale (language, script, country and variant) and appended to + * the base name. Typically, this will look like the following: + * + *

+     *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
+     *     baseName + "_" + language + "_" + script + "_" + country
+     *     baseName + "_" + language + "_" + script
+     *     baseName + "_" + language + "_" + country + "_" + variant
+     *     baseName + "_" + language + "_" + country
+     *     baseName + "_" + language
+     * 
+ * + *

Candidate bundle names where the final component is an empty string + * are omitted, along with the underscore. For example, if country is an + * empty string, the second and the fifth candidate bundle names above + * would be omitted. Also, if script is an empty string, the candidate names + * including script are omitted. For example, a locale with language "de" + * and variant "JAVA" will produce candidate names with base name + * "MyResource" below. + * + *

+     *     MyResource_de__JAVA
+     *     MyResource_de
+     * 
+ * + * In the case that the variant contains one or more underscores ('_'), a + * sequence of bundle names generated by truncating the last underscore and + * the part following it is inserted after a candidate bundle name with the + * original variant. For example, for a locale with language "en", script + * "Latn, country "US" and variant "WINDOWS_VISTA", and bundle base name + * "MyResource", the list of candidate bundle names below is generated: + * + *
+     * MyResource_en_Latn_US_WINDOWS_VISTA
+     * MyResource_en_Latn_US_WINDOWS
+     * MyResource_en_Latn_US
+     * MyResource_en_Latn
+     * MyResource_en_US_WINDOWS_VISTA
+     * MyResource_en_US_WINDOWS
+     * MyResource_en_US
+     * MyResource_en
+     * 
+ * + *
Note: For some Locales, the list of + * candidate bundle names contains extra names, or the order of bundle names + * is slightly modified. See the description of the default implementation + * of {@link Control#getCandidateLocales(String, Locale) + * getCandidateLocales} for details.
+ * + *

getBundle then iterates over the candidate bundle names + * to find the first one for which it can instantiate an actual + * resource bundle. It uses the default controls' {@link Control#getFormats + * getFormats} method, which generates two bundle names for each generated + * name, the first a class name and the second a properties file name. For + * each candidate bundle name, it attempts to create a resource bundle: + * + *

  • First, it attempts to load a class using the generated class name. + * If such a class can be found and loaded using the specified class + * loader, is assignment compatible with ResourceBundle, is accessible from + * ResourceBundle, and can be instantiated, getBundle creates a + * new instance of this class and uses it as the result resource + * bundle. + * + *
  • Otherwise, getBundle attempts to locate a property + * resource file using the generated properties file name. It generates a + * path name from the candidate bundle name by replacing all "." characters + * with "/" and appending the string ".properties". It attempts to find a + * "resource" with this name using {@link + * java.lang.ClassLoader#getResource(java.lang.String) + * ClassLoader.getResource}. (Note that a "resource" in the sense of + * getResource has nothing to do with the contents of a + * resource bundle, it is just a container of data, such as a file.) If it + * finds a "resource", it attempts to create a new {@link + * PropertyResourceBundle} instance from its contents. If successful, this + * instance becomes the result resource bundle.
+ * + *

This continues until a result resource bundle is instantiated or the + * list of candidate bundle names is exhausted. If no matching resource + * bundle is found, the default control's {@link Control#getFallbackLocale + * getFallbackLocale} method is called, which returns the current default + * locale. A new sequence of candidate locale names is generated using this + * locale and and searched again, as above. + * + *

If still no result bundle is found, the base name alone is looked up. If + * this still fails, a MissingResourceException is thrown. + * + *

Once a result resource bundle has been found, + * its parent chain is instantiated. If the result bundle already + * has a parent (perhaps because it was returned from a cache) the chain is + * complete. + * + *

Otherwise, getBundle examines the remainder of the + * candidate locale list that was used during the pass that generated the + * result resource bundle. (As before, candidate bundle names where the + * final component is an empty string are omitted.) When it comes to the + * end of the candidate list, it tries the plain bundle name. With each of the + * candidate bundle names it attempts to instantiate a resource bundle (first + * looking for a class and then a properties file, as described above). + * + *

Whenever it succeeds, it calls the previously instantiated resource + * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method + * with the new resource bundle. This continues until the list of names + * is exhausted or the current bundle already has a non-null parent. + * + *

Once the parent chain is complete, the bundle is returned. + * + *

Note: getBundle caches instantiated resource + * bundles and might return the same resource bundle instance multiple times. + * + *

Note:The baseName argument should be a fully + * qualified class name. However, for compatibility with earlier versions, + * Sun's Java SE Runtime Environments do not verify this, and so it is + * possible to access PropertyResourceBundles by specifying a + * path name (using "/") instead of a fully qualified class name (using + * "."). + * + *

+ * Example: + *

+ * The following class and property files are provided: + *

+     *     MyResources.class
+     *     MyResources.properties
+     *     MyResources_fr.properties
+     *     MyResources_fr_CH.class
+     *     MyResources_fr_CH.properties
+     *     MyResources_en.properties
+     *     MyResources_es_ES.class
+     * 
+ * + * The contents of all files are valid (that is, public non-abstract + * subclasses of ResourceBundle for the ".class" files, + * syntactically correct ".properties" files). The default locale is + * Locale("en", "GB"). + * + *

Calling getBundle with the locale arguments below will + * instantiate resource bundles as follows: + * + * + * + * + * + * + * + *
Locale("fr", "CH")MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class
Locale("fr", "FR")MyResources_fr.properties, parent MyResources.class
Locale("de", "DE")MyResources_en.properties, parent MyResources.class
Locale("en", "US")MyResources_en.properties, parent MyResources.class
Locale("es", "ES")MyResources_es_ES.class, parent MyResources.class
+ * + *

The file MyResources_fr_CH.properties is never used because it is + * hidden by the MyResources_fr_CH.class. Likewise, MyResources.properties + * is also hidden by MyResources.class. + * + * @param baseName the base name of the resource bundle, a fully qualified class name + * @param locale the locale for which a resource bundle is desired + * @param loader the class loader from which to load the resource bundle + * @return a resource bundle for the given base name and locale + * @exception java.lang.NullPointerException + * if baseName, locale, or loader is null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @since 1.2 + */ + public static ResourceBundle getBundle(String baseName, Locale locale, + ClassLoader loader) + { + if (loader == null) { + throw new NullPointerException(); + } + return getBundleImpl(baseName, locale, loader, Control.INSTANCE); + } + + /** + * Returns a resource bundle using the specified base name, target + * locale, class loader and control. Unlike the {@linkplain + * #getBundle(String, Locale, ClassLoader) getBundle + * factory methods with no control argument}, the given + * control specifies how to locate and instantiate resource + * bundles. Conceptually, the bundle loading process with the given + * control is performed in the following steps. + * + *

+ *

    + *
  1. This factory method looks up the resource bundle in the cache for + * the specified baseName, targetLocale and + * loader. If the requested resource bundle instance is + * found in the cache and the time-to-live periods of the instance and + * all of its parent instances have not expired, the instance is returned + * to the caller. Otherwise, this factory method proceeds with the + * loading process below.
  2. + * + *
  3. The {@link ResourceBundle.Control#getFormats(String) + * control.getFormats} method is called to get resource bundle formats + * to produce bundle or resource names. The strings + * "java.class" and "java.properties" + * designate class-based and {@linkplain PropertyResourceBundle + * property}-based resource bundles, respectively. Other strings + * starting with "java." are reserved for future extensions + * and must not be used for application-defined formats. Other strings + * designate application-defined formats.
  4. + * + *
  5. The {@link ResourceBundle.Control#getCandidateLocales(String, + * Locale) control.getCandidateLocales} method is called with the target + * locale to get a list of candidate Locales for + * which resource bundles are searched.
  6. + * + *
  7. The {@link ResourceBundle.Control#newBundle(String, Locale, + * String, ClassLoader, boolean) control.newBundle} method is called to + * instantiate a ResourceBundle for the base bundle name, a + * candidate locale, and a format. (Refer to the note on the cache + * lookup below.) This step is iterated over all combinations of the + * candidate locales and formats until the newBundle method + * returns a ResourceBundle instance or the iteration has + * used up all the combinations. For example, if the candidate locales + * are Locale("de", "DE"), Locale("de") and + * Locale("") and the formats are "java.class" + * and "java.properties", then the following is the + * sequence of locale-format combinations to be used to call + * control.newBundle. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Locale
    + *
    format
    + *
    Locale("de", "DE")
    + *
    java.class
    + *
    Locale("de", "DE")java.properties
    + *
    Locale("de")java.class
    Locale("de")java.properties
    Locale("")
    + *
    java.class
    Locale("")java.properties
    + *
  8. + * + *
  9. If the previous step has found no resource bundle, proceed to + * Step 6. If a bundle has been found that is a base bundle (a bundle + * for Locale("")), and the candidate locale list only contained + * Locale(""), return the bundle to the caller. If a bundle + * has been found that is a base bundle, but the candidate locale list + * contained locales other than Locale(""), put the bundle on hold and + * proceed to Step 6. If a bundle has been found that is not a base + * bundle, proceed to Step 7.
  10. + * + *
  11. The {@link ResourceBundle.Control#getFallbackLocale(String, + * Locale) control.getFallbackLocale} method is called to get a fallback + * locale (alternative to the current target locale) to try further + * finding a resource bundle. If the method returns a non-null locale, + * it becomes the next target locale and the loading process starts over + * from Step 3. Otherwise, if a base bundle was found and put on hold in + * a previous Step 5, it is returned to the caller now. Otherwise, a + * MissingResourceException is thrown.
  12. + * + *
  13. At this point, we have found a resource bundle that's not the + * base bundle. If this bundle set its parent during its instantiation, + * it is returned to the caller. Otherwise, its parent chain is + * instantiated based on the list of candidate locales from which it was + * found. Finally, the bundle is returned to the caller.
  14. + *
+ * + *

During the resource bundle loading process above, this factory + * method looks up the cache before calling the {@link + * Control#newBundle(String, Locale, String, ClassLoader, boolean) + * control.newBundle} method. If the time-to-live period of the + * resource bundle found in the cache has expired, the factory method + * calls the {@link ResourceBundle.Control#needsReload(String, Locale, + * String, ClassLoader, ResourceBundle, long) control.needsReload} + * method to determine whether the resource bundle needs to be reloaded. + * If reloading is required, the factory method calls + * control.newBundle to reload the resource bundle. If + * control.newBundle returns null, the factory + * method puts a dummy resource bundle in the cache as a mark of + * nonexistent resource bundles in order to avoid lookup overhead for + * subsequent requests. Such dummy resource bundles are under the same + * expiration control as specified by control. + * + *

All resource bundles loaded are cached by default. Refer to + * {@link Control#getTimeToLive(String,Locale) + * control.getTimeToLive} for details. + * + *

The following is an example of the bundle loading process with the + * default ResourceBundle.Control implementation. + * + *

Conditions: + *

    + *
  • Base bundle name: foo.bar.Messages + *
  • Requested Locale: {@link Locale#ITALY}
  • + *
  • Default Locale: {@link Locale#FRENCH}
  • + *
  • Available resource bundles: + * foo/bar/Messages_fr.properties and + * foo/bar/Messages.properties
  • + *
+ * + *

First, getBundle tries loading a resource bundle in + * the following sequence. + * + *

    + *
  • class foo.bar.Messages_it_IT + *
  • file foo/bar/Messages_it_IT.properties + *
  • class foo.bar.Messages_it
  • + *
  • file foo/bar/Messages_it.properties
  • + *
  • class foo.bar.Messages
  • + *
  • file foo/bar/Messages.properties
  • + *
+ * + *

At this point, getBundle finds + * foo/bar/Messages.properties, which is put on hold + * because it's the base bundle. getBundle calls {@link + * Control#getFallbackLocale(String, Locale) + * control.getFallbackLocale("foo.bar.Messages", Locale.ITALY)} which + * returns Locale.FRENCH. Next, getBundle + * tries loading a bundle in the following sequence. + * + *

    + *
  • class foo.bar.Messages_fr
  • + *
  • file foo/bar/Messages_fr.properties
  • + *
  • class foo.bar.Messages
  • + *
  • file foo/bar/Messages.properties
  • + *
+ * + *

getBundle finds + * foo/bar/Messages_fr.properties and creates a + * ResourceBundle instance. Then, getBundle + * sets up its parent chain from the list of the candiate locales. Only + * foo/bar/Messages.properties is found in the list and + * getBundle creates a ResourceBundle instance + * that becomes the parent of the instance for + * foo/bar/Messages_fr.properties. + * + * @param baseName + * the base name of the resource bundle, a fully qualified + * class name + * @param targetLocale + * the locale for which a resource bundle is desired + * @param loader + * the class loader from which to load the resource bundle + * @param control + * the control which gives information for the resource + * bundle loading process + * @return a resource bundle for the given base name and locale + * @exception NullPointerException + * if baseName, targetLocale, + * loader, or control is + * null + * @exception MissingResourceException + * if no resource bundle for the specified base name can be found + * @exception IllegalArgumentException + * if the given control doesn't perform properly + * (e.g., control.getCandidateLocales returns null.) + * Note that validation of control is performed as + * needed. + * @since 1.6 + */ + public static ResourceBundle getBundle(String baseName, Locale targetLocale, + ClassLoader loader, Control control) { + if (loader == null || control == null) { + throw new NullPointerException(); + } + return getBundleImpl(baseName, targetLocale, loader, control); + } + + private static ResourceBundle getBundleImpl(String baseName, Locale locale, + ClassLoader loader, Control control) { + if (locale == null || control == null) { + throw new NullPointerException(); + } + + // We create a CacheKey here for use by this call. The base + // name and loader will never change during the bundle loading + // process. We have to make sure that the locale is set before + // using it as a cache key. + CacheKey cacheKey = new CacheKey(baseName, locale, loader); + ResourceBundle bundle = null; + + // Quick lookup of the cache. + BundleReference bundleRef = cacheList.get(cacheKey); + if (bundleRef != null) { + bundle = bundleRef.get(); + bundleRef = null; + } + + // If this bundle and all of its parents are valid (not expired), + // then return this bundle. If any of the bundles is expired, we + // don't call control.needsReload here but instead drop into the + // complete loading process below. + if (isValidBundle(bundle) && hasValidParentChain(bundle)) { + return bundle; + } + + // No valid bundle was found in the cache, so we need to load the + // resource bundle and its parents. + + boolean isKnownControl = (control == Control.INSTANCE) || + (control instanceof SingleFormatControl); + List formats = control.getFormats(baseName); + if (!isKnownControl && !checkList(formats)) { + throw new IllegalArgumentException("Invalid Control: getFormats"); + } + + ResourceBundle baseBundle = null; + for (Locale targetLocale = locale; + targetLocale != null; + targetLocale = control.getFallbackLocale(baseName, targetLocale)) { + List candidateLocales = control.getCandidateLocales(baseName, targetLocale); + if (!isKnownControl && !checkList(candidateLocales)) { + throw new IllegalArgumentException("Invalid Control: getCandidateLocales"); + } + + bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle); + + // If the loaded bundle is the base bundle and exactly for the + // requested locale or the only candidate locale, then take the + // bundle as the resulting one. If the loaded bundle is the base + // bundle, it's put on hold until we finish processing all + // fallback locales. + if (isValidBundle(bundle)) { + boolean isBaseBundle = Locale.ROOT.equals(bundle.locale); + if (!isBaseBundle || bundle.locale.equals(locale) + || (candidateLocales.size() == 1 + && bundle.locale.equals(candidateLocales.get(0)))) { + break; + } + + // If the base bundle has been loaded, keep the reference in + // baseBundle so that we can avoid any redundant loading in case + // the control specify not to cache bundles. + if (isBaseBundle && baseBundle == null) { + baseBundle = bundle; + } + } + } + + if (bundle == null) { + if (baseBundle == null) { + throwMissingResourceException(baseName, locale, cacheKey.getCause()); + } + bundle = baseBundle; + } + + return bundle; + } + + /** + * Checks if the given List is not null, not empty, + * not having null in its elements. + */ + private static final boolean checkList(List a) { + boolean valid = (a != null && a.size() != 0); + if (valid) { + int size = a.size(); + for (int i = 0; valid && i < size; i++) { + valid = (a.get(i) != null); + } + } + return valid; + } + + private static final ResourceBundle findBundle(CacheKey cacheKey, + List candidateLocales, + List formats, + int index, + Control control, + ResourceBundle baseBundle) { + Locale targetLocale = candidateLocales.get(index); + ResourceBundle parent = null; + if (index != candidateLocales.size() - 1) { + parent = findBundle(cacheKey, candidateLocales, formats, index + 1, + control, baseBundle); + } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) { + return baseBundle; + } + + // Before we do the real loading work, see whether we need to + // do some housekeeping: If references to class loaders or + // resource bundles have been nulled out, remove all related + // information from the cache. + Object ref; + while ((ref = referenceQueue.poll()) != null) { + cacheList.remove(((CacheKeyReference)ref).getCacheKey()); + } + + // flag indicating the resource bundle has expired in the cache + boolean expiredBundle = false; + + // First, look up the cache to see if it's in the cache, without + // attempting to load bundle. + cacheKey.setLocale(targetLocale); + ResourceBundle bundle = findBundleInCache(cacheKey, control); + if (isValidBundle(bundle)) { + expiredBundle = bundle.expired; + if (!expiredBundle) { + // If its parent is the one asked for by the candidate + // locales (the runtime lookup path), we can take the cached + // one. (If it's not identical, then we'd have to check the + // parent's parents to be consistent with what's been + // requested.) + if (bundle.parent == parent) { + return bundle; + } + // Otherwise, remove the cached one since we can't keep + // the same bundles having different parents. + BundleReference bundleRef = cacheList.get(cacheKey); + if (bundleRef != null && bundleRef.get() == bundle) { + cacheList.remove(cacheKey, bundleRef); + } + } + } + + if (bundle != NONEXISTENT_BUNDLE) { + CacheKey constKey = (CacheKey) cacheKey.clone(); + + try { + bundle = loadBundle(cacheKey, formats, control, expiredBundle); + if (bundle != null) { + if (bundle.parent == null) { + bundle.setParent(parent); + } + bundle.locale = targetLocale; + bundle = putBundleInCache(cacheKey, bundle, control); + return bundle; + } + + // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle + // instance for the locale. + putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control); + } finally { + if (constKey.getCause() instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + } + } + return parent; + } + + private static final ResourceBundle loadBundle(CacheKey cacheKey, + List formats, + Control control, + boolean reload) { + + // Here we actually load the bundle in the order of formats + // specified by the getFormats() value. + Locale targetLocale = cacheKey.getLocale(); + + ResourceBundle bundle = null; + int size = formats.size(); + for (int i = 0; i < size; i++) { + String format = formats.get(i); + try { + bundle = control.newBundle(cacheKey.getName(), targetLocale, format, + cacheKey.getLoader(), reload); + } catch (LinkageError error) { + // We need to handle the LinkageError case due to + // inconsistent case-sensitivity in ClassLoader. + // See 6572242 for details. + cacheKey.setCause(error); + } catch (Exception cause) { + cacheKey.setCause(cause); + } + if (bundle != null) { + // Set the format in the cache key so that it can be + // used when calling needsReload later. + cacheKey.setFormat(format); + bundle.name = cacheKey.getName(); + bundle.locale = targetLocale; + // Bundle provider might reuse instances. So we should make + // sure to clear the expired flag here. + bundle.expired = false; + break; + } + } + + return bundle; + } + + private static final boolean isValidBundle(ResourceBundle bundle) { + return bundle != null && bundle != NONEXISTENT_BUNDLE; + } + + /** + * Determines whether any of resource bundles in the parent chain, + * including the leaf, have expired. + */ + private static final boolean hasValidParentChain(ResourceBundle bundle) { + long now = System.currentTimeMillis(); + while (bundle != null) { + if (bundle.expired) { + return false; + } + CacheKey key = bundle.cacheKey; + if (key != null) { + long expirationTime = key.expirationTime; + if (expirationTime >= 0 && expirationTime <= now) { + return false; + } + } + bundle = bundle.parent; + } + return true; + } + + /** + * Throw a MissingResourceException with proper message + */ + private static final void throwMissingResourceException(String baseName, + Locale locale, + Throwable cause) { + // If the cause is a MissingResourceException, avoid creating + // a long chain. (6355009) + if (cause instanceof MissingResourceException) { + cause = null; + } + throw new MissingResourceException("Can't find bundle for base name " + + baseName + ", locale " + locale, + baseName + "_" + locale, // className + "", // key + cause); + } + + /** + * Finds a bundle in the cache. Any expired bundles are marked as + * `expired' and removed from the cache upon return. + * + * @param cacheKey the key to look up the cache + * @param control the Control to be used for the expiration control + * @return the cached bundle, or null if the bundle is not found in the + * cache or its parent has expired. bundle.expire is true + * upon return if the bundle in the cache has expired. + */ + private static final ResourceBundle findBundleInCache(CacheKey cacheKey, + Control control) { + BundleReference bundleRef = cacheList.get(cacheKey); + if (bundleRef == null) { + return null; + } + ResourceBundle bundle = bundleRef.get(); + if (bundle == null) { + return null; + } + ResourceBundle p = bundle.parent; + assert p != NONEXISTENT_BUNDLE; + // If the parent has expired, then this one must also expire. We + // check only the immediate parent because the actual loading is + // done from the root (base) to leaf (child) and the purpose of + // checking is to propagate expiration towards the leaf. For + // example, if the requested locale is ja_JP_JP and there are + // bundles for all of the candidates in the cache, we have a list, + // + // base <- ja <- ja_JP <- ja_JP_JP + // + // If ja has expired, then it will reload ja and the list becomes a + // tree. + // + // base <- ja (new) + // " <- ja (expired) <- ja_JP <- ja_JP_JP + // + // When looking up ja_JP in the cache, it finds ja_JP in the cache + // which references to the expired ja. Then, ja_JP is marked as + // expired and removed from the cache. This will be propagated to + // ja_JP_JP. + // + // Now, it's possible, for example, that while loading new ja_JP, + // someone else has started loading the same bundle and finds the + // base bundle has expired. Then, what we get from the first + // getBundle call includes the expired base bundle. However, if + // someone else didn't start its loading, we wouldn't know if the + // base bundle has expired at the end of the loading process. The + // expiration control doesn't guarantee that the returned bundle and + // its parents haven't expired. + // + // We could check the entire parent chain to see if there's any in + // the chain that has expired. But this process may never end. An + // extreme case would be that getTimeToLive returns 0 and + // needsReload always returns true. + if (p != null && p.expired) { + assert bundle != NONEXISTENT_BUNDLE; + bundle.expired = true; + bundle.cacheKey = null; + cacheList.remove(cacheKey, bundleRef); + bundle = null; + } else { + CacheKey key = bundleRef.getCacheKey(); + long expirationTime = key.expirationTime; + if (!bundle.expired && expirationTime >= 0 && + expirationTime <= System.currentTimeMillis()) { + // its TTL period has expired. + if (bundle != NONEXISTENT_BUNDLE) { + // Synchronize here to call needsReload to avoid + // redundant concurrent calls for the same bundle. + synchronized (bundle) { + expirationTime = key.expirationTime; + if (!bundle.expired && expirationTime >= 0 && + expirationTime <= System.currentTimeMillis()) { + try { + bundle.expired = control.needsReload(key.getName(), + key.getLocale(), + key.getFormat(), + key.getLoader(), + bundle, + key.loadTime); + } catch (Exception e) { + cacheKey.setCause(e); + } + if (bundle.expired) { + // If the bundle needs to be reloaded, then + // remove the bundle from the cache, but + // return the bundle with the expired flag + // on. + bundle.cacheKey = null; + cacheList.remove(cacheKey, bundleRef); + } else { + // Update the expiration control info. and reuse + // the same bundle instance + setExpirationTime(key, control); + } + } + } + } else { + // We just remove NONEXISTENT_BUNDLE from the cache. + cacheList.remove(cacheKey, bundleRef); + bundle = null; + } + } + } + return bundle; + } + + /** + * Put a new bundle in the cache. + * + * @param cacheKey the key for the resource bundle + * @param bundle the resource bundle to be put in the cache + * @return the ResourceBundle for the cacheKey; if someone has put + * the bundle before this call, the one found in the cache is + * returned. + */ + private static final ResourceBundle putBundleInCache(CacheKey cacheKey, + ResourceBundle bundle, + Control control) { + setExpirationTime(cacheKey, control); + if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) { + CacheKey key = (CacheKey) cacheKey.clone(); + BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key); + bundle.cacheKey = key; + + // Put the bundle in the cache if it's not been in the cache. + BundleReference result = cacheList.putIfAbsent(key, bundleRef); + + // If someone else has put the same bundle in the cache before + // us and it has not expired, we should use the one in the cache. + if (result != null) { + ResourceBundle rb = result.get(); + if (rb != null && !rb.expired) { + // Clear the back link to the cache key + bundle.cacheKey = null; + bundle = rb; + // Clear the reference in the BundleReference so that + // it won't be enqueued. + bundleRef.clear(); + } else { + // Replace the invalid (garbage collected or expired) + // instance with the valid one. + cacheList.put(key, bundleRef); + } + } + } + return bundle; + } + + private static final void setExpirationTime(CacheKey cacheKey, Control control) { + long ttl = control.getTimeToLive(cacheKey.getName(), + cacheKey.getLocale()); + if (ttl >= 0) { + // If any expiration time is specified, set the time to be + // expired in the cache. + long now = System.currentTimeMillis(); + cacheKey.loadTime = now; + cacheKey.expirationTime = now + ttl; + } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) { + cacheKey.expirationTime = ttl; + } else { + throw new IllegalArgumentException("Invalid Control: TTL=" + ttl); + } + } + + /** + * Removes all resource bundles from the cache that have been loaded + * using the caller's class loader. + * + * @since 1.6 + * @see ResourceBundle.Control#getTimeToLive(String,Locale) + */ + public static final void clearCache() { + clearCache(getLoader()); + } + + /** + * Removes all resource bundles from the cache that have been loaded + * using the given class loader. + * + * @param loader the class loader + * @exception NullPointerException if loader is null + * @since 1.6 + * @see ResourceBundle.Control#getTimeToLive(String,Locale) + */ + public static final void clearCache(ClassLoader loader) { + if (loader == null) { + throw new NullPointerException(); + } + Set set = cacheList.keySet(); + for (CacheKey key : set) { + if (key.getLoader() == loader) { + set.remove(key); + } + } + } + + /** + * Gets an object for the given key from this resource bundle. + * Returns null if this resource bundle does not contain an + * object for the given key. + * + * @param key the key for the desired object + * @exception NullPointerException if key is null + * @return the object for the given key, or null + */ + protected abstract Object handleGetObject(String key); + + /** + * Returns an enumeration of the keys. + * + * @return an Enumeration of the keys contained in + * this ResourceBundle and its parent bundles. + */ + public abstract Enumeration getKeys(); + + /** + * Determines whether the given key is contained in + * this ResourceBundle or its parent bundles. + * + * @param key + * the resource key + * @return true if the given key is + * contained in this ResourceBundle or its + * parent bundles; false otherwise. + * @exception NullPointerException + * if key is null + * @since 1.6 + */ + public boolean containsKey(String key) { + if (key == null) { + throw new NullPointerException(); + } + for (ResourceBundle rb = this; rb != null; rb = rb.parent) { + if (rb.handleKeySet().contains(key)) { + return true; + } + } + return false; + } + + /** + * Returns a Set of all keys contained in this + * ResourceBundle and its parent bundles. + * + * @return a Set of all keys contained in this + * ResourceBundle and its parent bundles. + * @since 1.6 + */ + public Set keySet() { + Set keys = new HashSet<>(); + for (ResourceBundle rb = this; rb != null; rb = rb.parent) { + keys.addAll(rb.handleKeySet()); + } + return keys; + } + + /** + * Returns a Set of the keys contained only + * in this ResourceBundle. + * + *

The default implementation returns a Set of the + * keys returned by the {@link #getKeys() getKeys} method except + * for the ones for which the {@link #handleGetObject(String) + * handleGetObject} method returns null. Once the + * Set has been created, the value is kept in this + * ResourceBundle in order to avoid producing the + * same Set in subsequent calls. Subclasses can + * override this method for faster handling. + * + * @return a Set of the keys contained only in this + * ResourceBundle + * @since 1.6 + */ + protected Set handleKeySet() { + if (keySet == null) { + synchronized (this) { + if (keySet == null) { + Set keys = new HashSet<>(); + Enumeration enumKeys = getKeys(); + while (enumKeys.hasMoreElements()) { + String key = enumKeys.nextElement(); + if (handleGetObject(key) != null) { + keys.add(key); + } + } + keySet = keys; + } + } + } + return keySet; + } + + + + /** + * ResourceBundle.Control defines a set of callback methods + * that are invoked by the {@link ResourceBundle#getBundle(String, + * Locale, ClassLoader, Control) ResourceBundle.getBundle} factory + * methods during the bundle loading process. In other words, a + * ResourceBundle.Control collaborates with the factory + * methods for loading resource bundles. The default implementation of + * the callback methods provides the information necessary for the + * factory methods to perform the default behavior. + * + *

In addition to the callback methods, the {@link + * #toBundleName(String, Locale) toBundleName} and {@link + * #toResourceName(String, String) toResourceName} methods are defined + * primarily for convenience in implementing the callback + * methods. However, the toBundleName method could be + * overridden to provide different conventions in the organization and + * packaging of localized resources. The toResourceName + * method is final to avoid use of wrong resource and class + * name separators. + * + *

Two factory methods, {@link #getControl(List)} and {@link + * #getNoFallbackControl(List)}, provide + * ResourceBundle.Control instances that implement common + * variations of the default bundle loading process. + * + *

The formats returned by the {@link Control#getFormats(String) + * getFormats} method and candidate locales returned by the {@link + * ResourceBundle.Control#getCandidateLocales(String, Locale) + * getCandidateLocales} method must be consistent in all + * ResourceBundle.getBundle invocations for the same base + * bundle. Otherwise, the ResourceBundle.getBundle methods + * may return unintended bundles. For example, if only + * "java.class" is returned by the getFormats + * method for the first call to ResourceBundle.getBundle + * and only "java.properties" for the second call, then the + * second call will return the class-based one that has been cached + * during the first call. + * + *

A ResourceBundle.Control instance must be thread-safe + * if it's simultaneously used by multiple threads. + * ResourceBundle.getBundle does not synchronize to call + * the ResourceBundle.Control methods. The default + * implementations of the methods are thread-safe. + * + *

Applications can specify ResourceBundle.Control + * instances returned by the getControl factory methods or + * created from a subclass of ResourceBundle.Control to + * customize the bundle loading process. The following are examples of + * changing the default bundle loading process. + * + *

Example 1 + * + *

The following code lets ResourceBundle.getBundle look + * up only properties-based resources. + * + *

+     * import java.util.*;
+     * import static java.util.ResourceBundle.Control.*;
+     * ...
+     * ResourceBundle bundle =
+     *   ResourceBundle.getBundle("MyResources", new Locale("fr", "CH"),
+     *                            ResourceBundle.Control.getControl(FORMAT_PROPERTIES));
+     * 
+ * + * Given the resource bundles in the example in + * the ResourceBundle.getBundle description, this + * ResourceBundle.getBundle call loads + * MyResources_fr_CH.properties whose parent is + * MyResources_fr.properties whose parent is + * MyResources.properties. (MyResources_fr_CH.properties + * is not hidden, but MyResources_fr_CH.class is.) + * + *

Example 2 + * + *

The following is an example of loading XML-based bundles + * using {@link Properties#loadFromXML(java.io.InputStream) + * Properties.loadFromXML}. + * + *

+     * ResourceBundle rb = ResourceBundle.getBundle("Messages",
+     *     new ResourceBundle.Control() {
+     *         public List<String> getFormats(String baseName) {
+     *             if (baseName == null)
+     *                 throw new NullPointerException();
+     *             return Arrays.asList("xml");
+     *         }
+     *         public ResourceBundle newBundle(String baseName,
+     *                                         Locale locale,
+     *                                         String format,
+     *                                         ClassLoader loader,
+     *                                         boolean reload)
+     *                          throws IllegalAccessException,
+     *                                 InstantiationException,
+     *                                 IOException {
+     *             if (baseName == null || locale == null
+     *                   || format == null || loader == null)
+     *                 throw new NullPointerException();
+     *             ResourceBundle bundle = null;
+     *             if (format.equals("xml")) {
+     *                 String bundleName = toBundleName(baseName, locale);
+     *                 String resourceName = toResourceName(bundleName, format);
+     *                 InputStream stream = null;
+     *                 if (reload) {
+     *                     URL url = loader.getResource(resourceName);
+     *                     if (url != null) {
+     *                         URLConnection connection = url.openConnection();
+     *                         if (connection != null) {
+     *                             // Disable caches to get fresh data for
+     *                             // reloading.
+     *                             connection.setUseCaches(false);
+     *                             stream = connection.getInputStream();
+     *                         }
+     *                     }
+     *                 } else {
+     *                     stream = loader.getResourceAsStream(resourceName);
+     *                 }
+     *                 if (stream != null) {
+     *                     BufferedInputStream bis = new BufferedInputStream(stream);
+     *                     bundle = new XMLResourceBundle(bis);
+     *                     bis.close();
+     *                 }
+     *             }
+     *             return bundle;
+     *         }
+     *     });
+     *
+     * ...
+     *
+     * private static class XMLResourceBundle extends ResourceBundle {
+     *     private Properties props;
+     *     XMLResourceBundle(InputStream stream) throws IOException {
+     *         props = new Properties();
+     *         props.loadFromXML(stream);
+     *     }
+     *     protected Object handleGetObject(String key) {
+     *         return props.getProperty(key);
+     *     }
+     *     public Enumeration<String> getKeys() {
+     *         ...
+     *     }
+     * }
+     * 
+ * + * @since 1.6 + */ + public static class Control { + /** + * The default format List, which contains the strings + * "java.class" and "java.properties", in + * this order. This List is {@linkplain + * Collections#unmodifiableList(List) unmodifiable}. + * + * @see #getFormats(String) + */ + public static final List FORMAT_DEFAULT + = Collections.unmodifiableList(Arrays.asList("java.class", + "java.properties")); + + /** + * The class-only format List containing + * "java.class". This List is {@linkplain + * Collections#unmodifiableList(List) unmodifiable}. + * + * @see #getFormats(String) + */ + public static final List FORMAT_CLASS + = Collections.unmodifiableList(Arrays.asList("java.class")); + + /** + * The properties-only format List containing + * "java.properties". This List is + * {@linkplain Collections#unmodifiableList(List) unmodifiable}. + * + * @see #getFormats(String) + */ + public static final List FORMAT_PROPERTIES + = Collections.unmodifiableList(Arrays.asList("java.properties")); + + /** + * The time-to-live constant for not caching loaded resource bundle + * instances. + * + * @see #getTimeToLive(String, Locale) + */ + public static final long TTL_DONT_CACHE = -1; + + /** + * The time-to-live constant for disabling the expiration control + * for loaded resource bundle instances in the cache. + * + * @see #getTimeToLive(String, Locale) + */ + public static final long TTL_NO_EXPIRATION_CONTROL = -2; + + private static final Control INSTANCE = new Control(); + + /** + * Sole constructor. (For invocation by subclass constructors, + * typically implicit.) + */ + protected Control() { + } + + /** + * Returns a ResourceBundle.Control in which the {@link + * #getFormats(String) getFormats} method returns the specified + * formats. The formats must be equal to + * one of {@link Control#FORMAT_PROPERTIES}, {@link + * Control#FORMAT_CLASS} or {@link + * Control#FORMAT_DEFAULT}. ResourceBundle.Control + * instances returned by this method are singletons and thread-safe. + * + *

Specifying {@link Control#FORMAT_DEFAULT} is equivalent to + * instantiating the ResourceBundle.Control class, + * except that this method returns a singleton. + * + * @param formats + * the formats to be returned by the + * ResourceBundle.Control.getFormats method + * @return a ResourceBundle.Control supporting the + * specified formats + * @exception NullPointerException + * if formats is null + * @exception IllegalArgumentException + * if formats is unknown + */ + public static final Control getControl(List formats) { + if (formats.equals(Control.FORMAT_PROPERTIES)) { + return SingleFormatControl.PROPERTIES_ONLY; + } + if (formats.equals(Control.FORMAT_CLASS)) { + return SingleFormatControl.CLASS_ONLY; + } + if (formats.equals(Control.FORMAT_DEFAULT)) { + return Control.INSTANCE; + } + throw new IllegalArgumentException(); + } + + /** + * Returns a ResourceBundle.Control in which the {@link + * #getFormats(String) getFormats} method returns the specified + * formats and the {@link + * Control#getFallbackLocale(String, Locale) getFallbackLocale} + * method returns null. The formats must + * be equal to one of {@link Control#FORMAT_PROPERTIES}, {@link + * Control#FORMAT_CLASS} or {@link Control#FORMAT_DEFAULT}. + * ResourceBundle.Control instances returned by this + * method are singletons and thread-safe. + * + * @param formats + * the formats to be returned by the + * ResourceBundle.Control.getFormats method + * @return a ResourceBundle.Control supporting the + * specified formats with no fallback + * Locale support + * @exception NullPointerException + * if formats is null + * @exception IllegalArgumentException + * if formats is unknown + */ + public static final Control getNoFallbackControl(List formats) { + if (formats.equals(Control.FORMAT_DEFAULT)) { + return NoFallbackControl.NO_FALLBACK; + } + if (formats.equals(Control.FORMAT_PROPERTIES)) { + return NoFallbackControl.PROPERTIES_ONLY_NO_FALLBACK; + } + if (formats.equals(Control.FORMAT_CLASS)) { + return NoFallbackControl.CLASS_ONLY_NO_FALLBACK; + } + throw new IllegalArgumentException(); + } + + /** + * Returns a List of Strings containing + * formats to be used to load resource bundles for the given + * baseName. The ResourceBundle.getBundle + * factory method tries to load resource bundles with formats in the + * order specified by the list. The list returned by this method + * must have at least one String. The predefined + * formats are "java.class" for class-based resource + * bundles and "java.properties" for {@linkplain + * PropertyResourceBundle properties-based} ones. Strings starting + * with "java." are reserved for future extensions and + * must not be used by application-defined formats. + * + *

It is not a requirement to return an immutable (unmodifiable) + * List. However, the returned List must + * not be mutated after it has been returned by + * getFormats. + * + *

The default implementation returns {@link #FORMAT_DEFAULT} so + * that the ResourceBundle.getBundle factory method + * looks up first class-based resource bundles, then + * properties-based ones. + * + * @param baseName + * the base name of the resource bundle, a fully qualified class + * name + * @return a List of Strings containing + * formats for loading resource bundles. + * @exception NullPointerException + * if baseName is null + * @see #FORMAT_DEFAULT + * @see #FORMAT_CLASS + * @see #FORMAT_PROPERTIES + */ + public List getFormats(String baseName) { + if (baseName == null) { + throw new NullPointerException(); + } + return FORMAT_DEFAULT; + } + + /** + * Returns a List of Locales as candidate + * locales for baseName and locale. This + * method is called by the ResourceBundle.getBundle + * factory method each time the factory method tries finding a + * resource bundle for a target Locale. + * + *

The sequence of the candidate locales also corresponds to the + * runtime resource lookup path (also known as the parent + * chain), if the corresponding resource bundles for the + * candidate locales exist and their parents are not defined by + * loaded resource bundles themselves. The last element of the list + * must be a {@linkplain Locale#ROOT root locale} if it is desired to + * have the base bundle as the terminal of the parent chain. + * + *

If the given locale is equal to Locale.ROOT (the + * root locale), a List containing only the root + * Locale must be returned. In this case, the + * ResourceBundle.getBundle factory method loads only + * the base bundle as the resulting resource bundle. + * + *

It is not a requirement to return an immutable (unmodifiable) + * List. However, the returned List must not + * be mutated after it has been returned by + * getCandidateLocales. + * + *

The default implementation returns a List containing + * Locales using the rules described below. In the + * description below, L, S, C and V + * respectively represent non-empty language, script, country, and + * variant. For example, [L, C] represents a + * Locale that has non-empty values only for language and + * country. The form L("xx") represents the (non-empty) + * language value is "xx". For all cases, Locales whose + * final component values are empty strings are omitted. + * + *

  1. For an input Locale with an empty script value, + * append candidate Locales by omitting the final component + * one by one as below: + * + *
      + *
    • [L, C, V] + *
    • [L, C] + *
    • [L] + *
    • Locale.ROOT + *
    + * + *
  2. For an input Locale with a non-empty script value, + * append candidate Locales by omitting the final component + * up to language, then append candidates generated from the + * Locale with country and variant restored: + * + *
      + *
    • [L, S, C, V] + *
    • [L, S, C] + *
    • [L, S] + *
    • [L, C, V] + *
    • [L, C] + *
    • [L] + *
    • Locale.ROOT + *
    + * + *
  3. For an input Locale with a variant value consisting + * of multiple subtags separated by underscore, generate candidate + * Locales by omitting the variant subtags one by one, then + * insert them after every occurence of Locales with the + * full variant value in the original list. For example, if the + * the variant consists of two subtags V1 and V2: + * + *
      + *
    • [L, S, C, V1, V2] + *
    • [L, S, C, V1] + *
    • [L, S, C] + *
    • [L, S] + *
    • [L, C, V1, V2] + *
    • [L, C, V1] + *
    • [L, C] + *
    • [L] + *
    • Locale.ROOT + *
    + * + *
  4. Special cases for Chinese. When an input Locale has the + * language "zh" (Chinese) and an empty script value, either "Hans" (Simplified) or + * "Hant" (Traditional) might be supplied, depending on the country. + * When the country is "CN" (China) or "SG" (Singapore), "Hans" is supplied. + * When the country is "HK" (Hong Kong SAR China), "MO" (Macau SAR China), + * or "TW" (Taiwan), "Hant" is supplied. For all other countries or when the country + * is empty, no script is supplied. For example, for Locale("zh", "CN") + * , the candidate list will be: + *
      + *
    • [L("zh"), S("Hans"), C("CN")] + *
    • [L("zh"), S("Hans")] + *
    • [L("zh"), C("CN")] + *
    • [L("zh")] + *
    • Locale.ROOT + *
    + * + * For Locale("zh", "TW"), the candidate list will be: + *
      + *
    • [L("zh"), S("Hant"), C("TW")] + *
    • [L("zh"), S("Hant")] + *
    • [L("zh"), C("TW")] + *
    • [L("zh")] + *
    • Locale.ROOT + *
    + * + *
  5. Special cases for Norwegian. Both Locale("no", "NO", + * "NY") and Locale("nn", "NO") represent Norwegian + * Nynorsk. When a locale's language is "nn", the standard candidate + * list is generated up to [L("nn")], and then the following + * candidates are added: + * + *
    • [L("no"), C("NO"), V("NY")] + *
    • [L("no"), C("NO")] + *
    • [L("no")] + *
    • Locale.ROOT + *
    + * + * If the locale is exactly Locale("no", "NO", "NY"), it is first + * converted to Locale("nn", "NO") and then the above procedure is + * followed. + * + *

    Also, Java treats the language "no" as a synonym of Norwegian + * Bokmål "nb". Except for the single case Locale("no", + * "NO", "NY") (handled above), when an input Locale + * has language "no" or "nb", candidate Locales with + * language code "no" and "nb" are interleaved, first using the + * requested language, then using its synonym. For example, + * Locale("nb", "NO", "POSIX") generates the following + * candidate list: + * + *

      + *
    • [L("nb"), C("NO"), V("POSIX")] + *
    • [L("no"), C("NO"), V("POSIX")] + *
    • [L("nb"), C("NO")] + *
    • [L("no"), C("NO")] + *
    • [L("nb")] + *
    • [L("no")] + *
    • Locale.ROOT + *
    + * + * Locale("no", "NO", "POSIX") would generate the same list + * except that locales with "no" would appear before the corresponding + * locales with "nb".
  6. + * + * + *
+ * + *

The default implementation uses an {@link ArrayList} that + * overriding implementations may modify before returning it to the + * caller. However, a subclass must not modify it after it has + * been returned by getCandidateLocales. + * + *

For example, if the given baseName is "Messages" + * and the given locale is + * Locale("ja", "", "XX"), then a + * List of Locales: + *

+         *     Locale("ja", "", "XX")
+         *     Locale("ja")
+         *     Locale.ROOT
+         * 
+ * is returned. And if the resource bundles for the "ja" and + * "" Locales are found, then the runtime resource + * lookup path (parent chain) is: + *
+         *     Messages_ja -> Messages
+         * 
+ * + * @param baseName + * the base name of the resource bundle, a fully + * qualified class name + * @param locale + * the locale for which a resource bundle is desired + * @return a List of candidate + * Locales for the given locale + * @exception NullPointerException + * if baseName or locale is + * null + */ + public List getCandidateLocales(String baseName, Locale locale) { + if (baseName == null) { + throw new NullPointerException(); + } + return new ArrayList<>(CANDIDATES_CACHE.get(locale.getBaseLocale())); + } + + private static final CandidateListCache CANDIDATES_CACHE = new CandidateListCache(); + + private static class CandidateListCache extends LocaleObjectCache> { + protected List createObject(BaseLocale base) { + String language = base.getLanguage(); + String script = base.getScript(); + String region = base.getRegion(); + String variant = base.getVariant(); + + // Special handling for Norwegian + boolean isNorwegianBokmal = false; + boolean isNorwegianNynorsk = false; + if (language.equals("no")) { + if (region.equals("NO") && variant.equals("NY")) { + variant = ""; + isNorwegianNynorsk = true; + } else { + isNorwegianBokmal = true; + } + } + if (language.equals("nb") || isNorwegianBokmal) { + List tmpList = getDefaultList("nb", script, region, variant); + // Insert a locale replacing "nb" with "no" for every list entry + List bokmalList = new LinkedList<>(); + for (Locale l : tmpList) { + bokmalList.add(l); + if (l.getLanguage().length() == 0) { + break; + } + bokmalList.add(Locale.getInstance("no", l.getScript(), l.getCountry(), + l.getVariant(), null)); + } + return bokmalList; + } else if (language.equals("nn") || isNorwegianNynorsk) { + // Insert no_NO_NY, no_NO, no after nn + List nynorskList = getDefaultList("nn", script, region, variant); + int idx = nynorskList.size() - 1; + nynorskList.add(idx++, Locale.getInstance("no", "NO", "NY")); + nynorskList.add(idx++, Locale.getInstance("no", "NO", "")); + nynorskList.add(idx++, Locale.getInstance("no", "", "")); + return nynorskList; + } + // Special handling for Chinese + else if (language.equals("zh")) { + if (script.length() == 0 && region.length() > 0) { + // Supply script for users who want to use zh_Hans/zh_Hant + // as bundle names (recommended for Java7+) + if (region.equals("TW") || region.equals("HK") || region.equals("MO")) { + script = "Hant"; + } else if (region.equals("CN") || region.equals("SG")) { + script = "Hans"; + } + } else if (script.length() > 0 && region.length() == 0) { + // Supply region(country) for users who still package Chinese + // bundles using old convension. + if (script.equals("Hans")) { + region = "CN"; + } else if (script.equals("Hant")) { + region = "TW"; + } + } + } + + return getDefaultList(language, script, region, variant); + } + + private static List getDefaultList(String language, String script, String region, String variant) { + List variants = null; + + if (variant.length() > 0) { + variants = new LinkedList<>(); + int idx = variant.length(); + while (idx != -1) { + variants.add(variant.substring(0, idx)); + idx = variant.lastIndexOf('_', --idx); + } + } + + List list = new LinkedList<>(); + + if (variants != null) { + for (String v : variants) { + list.add(Locale.getInstance(language, script, region, v, null)); + } + } + if (region.length() > 0) { + list.add(Locale.getInstance(language, script, region, "", null)); + } + if (script.length() > 0) { + list.add(Locale.getInstance(language, script, "", "", null)); + + // With script, after truncating variant, region and script, + // start over without script. + if (variants != null) { + for (String v : variants) { + list.add(Locale.getInstance(language, "", region, v, null)); + } + } + if (region.length() > 0) { + list.add(Locale.getInstance(language, "", region, "", null)); + } + } + if (language.length() > 0) { + list.add(Locale.getInstance(language, "", "", "", null)); + } + // Add root locale at the end + list.add(Locale.ROOT); + + return list; + } + } + + /** + * Returns a Locale to be used as a fallback locale for + * further resource bundle searches by the + * ResourceBundle.getBundle factory method. This method + * is called from the factory method every time when no resulting + * resource bundle has been found for baseName and + * locale, where locale is either the parameter for + * ResourceBundle.getBundle or the previous fallback + * locale returned by this method. + * + *

The method returns null if no further fallback + * search is desired. + * + *

The default implementation returns the {@linkplain + * Locale#getDefault() default Locale} if the given + * locale isn't the default one. Otherwise, + * null is returned. + * + * @param baseName + * the base name of the resource bundle, a fully + * qualified class name for which + * ResourceBundle.getBundle has been + * unable to find any resource bundles (except for the + * base bundle) + * @param locale + * the Locale for which + * ResourceBundle.getBundle has been + * unable to find any resource bundles (except for the + * base bundle) + * @return a Locale for the fallback search, + * or null if no further fallback search + * is desired. + * @exception NullPointerException + * if baseName or locale + * is null + */ + public Locale getFallbackLocale(String baseName, Locale locale) { + if (baseName == null) { + throw new NullPointerException(); + } + Locale defaultLocale = Locale.getDefault(); + return locale.equals(defaultLocale) ? null : defaultLocale; + } + + /** + * Instantiates a resource bundle for the given bundle name of the + * given format and locale, using the given class loader if + * necessary. This method returns null if there is no + * resource bundle available for the given parameters. If a resource + * bundle can't be instantiated due to an unexpected error, the + * error must be reported by throwing an Error or + * Exception rather than simply returning + * null. + * + *

If the reload flag is true, it + * indicates that this method is being called because the previously + * loaded resource bundle has expired. + * + *

The default implementation instantiates a + * ResourceBundle as follows. + * + *

    + * + *
  • The bundle name is obtained by calling {@link + * #toBundleName(String, Locale) toBundleName(baseName, + * locale)}.
  • + * + *
  • If format is "java.class", the + * {@link Class} specified by the bundle name is loaded by calling + * {@link ClassLoader#loadClass(String)}. Then, a + * ResourceBundle is instantiated by calling {@link + * Class#newInstance()}. Note that the reload flag is + * ignored for loading class-based resource bundles in this default + * implementation.
  • + * + *
  • If format is "java.properties", + * {@link #toResourceName(String, String) toResourceName(bundlename, + * "properties")} is called to get the resource name. + * If reload is true, {@link + * ClassLoader#getResource(String) load.getResource} is called + * to get a {@link URL} for creating a {@link + * URLConnection}. This URLConnection is used to + * {@linkplain URLConnection#setUseCaches(boolean) disable the + * caches} of the underlying resource loading layers, + * and to {@linkplain URLConnection#getInputStream() get an + * InputStream}. + * Otherwise, {@link ClassLoader#getResourceAsStream(String) + * loader.getResourceAsStream} is called to get an {@link + * InputStream}. Then, a {@link + * PropertyResourceBundle} is constructed with the + * InputStream.
  • + * + *
  • If format is neither "java.class" + * nor "java.properties", an + * IllegalArgumentException is thrown.
  • + * + *
+ * + * @param baseName + * the base bundle name of the resource bundle, a fully + * qualified class name + * @param locale + * the locale for which the resource bundle should be + * instantiated + * @param format + * the resource bundle format to be loaded + * @param loader + * the ClassLoader to use to load the bundle + * @param reload + * the flag to indicate bundle reloading; true + * if reloading an expired resource bundle, + * false otherwise + * @return the resource bundle instance, + * or null if none could be found. + * @exception NullPointerException + * if bundleName, locale, + * format, or loader is + * null, or if null is returned by + * {@link #toBundleName(String, Locale) toBundleName} + * @exception IllegalArgumentException + * if format is unknown, or if the resource + * found for the given parameters contains malformed data. + * @exception ClassCastException + * if the loaded class cannot be cast to ResourceBundle + * @exception IllegalAccessException + * if the class or its nullary constructor is not + * accessible. + * @exception InstantiationException + * if the instantiation of a class fails for some other + * reason. + * @exception ExceptionInInitializerError + * if the initialization provoked by this method fails. + * @exception SecurityException + * If a security manager is present and creation of new + * instances is denied. See {@link Class#newInstance()} + * for details. + * @exception IOException + * if an error occurred when reading resources using + * any I/O operations + */ + public ResourceBundle newBundle(String baseName, Locale locale, String format, + ClassLoader loader, boolean reload) + throws IllegalAccessException, InstantiationException, IOException { + String bundleName = toBundleName(baseName, locale); + ResourceBundle bundle = null; + if (format.equals("java.class")) { + try { + Class bundleClass + = (Class)loader.loadClass(bundleName); + + // If the class isn't a ResourceBundle subclass, throw a + // ClassCastException. + if (ResourceBundle.class.isAssignableFrom(bundleClass)) { + bundle = bundleClass.newInstance(); + } else { + throw new ClassCastException(bundleClass.getName() + + " cannot be cast to ResourceBundle"); + } + } catch (ClassNotFoundException e) { + } + } else if (format.equals("java.properties")) { + final String resourceName = toResourceName(bundleName, "properties"); + final ClassLoader classLoader = loader; + final boolean reloadFlag = reload; + InputStream stream = null; + try { + stream = AccessController.doPrivileged( + new PrivilegedExceptionAction() { + public InputStream run() throws IOException { + InputStream is = null; + if (reloadFlag) { + URL url = classLoader.getResource(resourceName); + if (url != null) { + URLConnection connection = url.openConnection(); + if (connection != null) { + // Disable caches to get fresh data for + // reloading. + connection.setUseCaches(false); + is = connection.getInputStream(); + } + } + } else { + is = classLoader.getResourceAsStream(resourceName); + } + return is; + } + }); + } catch (PrivilegedActionException e) { + throw (IOException) e.getException(); + } + if (stream != null) { + try { + bundle = new PropertyResourceBundle(stream); + } finally { + stream.close(); + } + } + } else { + throw new IllegalArgumentException("unknown format: " + format); + } + return bundle; + } + + /** + * Returns the time-to-live (TTL) value for resource bundles that + * are loaded under this + * ResourceBundle.Control. Positive time-to-live values + * specify the number of milliseconds a bundle can remain in the + * cache without being validated against the source data from which + * it was constructed. The value 0 indicates that a bundle must be + * validated each time it is retrieved from the cache. {@link + * #TTL_DONT_CACHE} specifies that loaded resource bundles are not + * put in the cache. {@link #TTL_NO_EXPIRATION_CONTROL} specifies + * that loaded resource bundles are put in the cache with no + * expiration control. + * + *

The expiration affects only the bundle loading process by the + * ResourceBundle.getBundle factory method. That is, + * if the factory method finds a resource bundle in the cache that + * has expired, the factory method calls the {@link + * #needsReload(String, Locale, String, ClassLoader, ResourceBundle, + * long) needsReload} method to determine whether the resource + * bundle needs to be reloaded. If needsReload returns + * true, the cached resource bundle instance is removed + * from the cache. Otherwise, the instance stays in the cache, + * updated with the new TTL value returned by this method. + * + *

All cached resource bundles are subject to removal from the + * cache due to memory constraints of the runtime environment. + * Returning a large positive value doesn't mean to lock loaded + * resource bundles in the cache. + * + *

The default implementation returns {@link #TTL_NO_EXPIRATION_CONTROL}. + * + * @param baseName + * the base name of the resource bundle for which the + * expiration value is specified. + * @param locale + * the locale of the resource bundle for which the + * expiration value is specified. + * @return the time (0 or a positive millisecond offset from the + * cached time) to get loaded bundles expired in the cache, + * {@link #TTL_NO_EXPIRATION_CONTROL} to disable the + * expiration control, or {@link #TTL_DONT_CACHE} to disable + * caching. + * @exception NullPointerException + * if baseName or locale is + * null + */ + public long getTimeToLive(String baseName, Locale locale) { + if (baseName == null || locale == null) { + throw new NullPointerException(); + } + return TTL_NO_EXPIRATION_CONTROL; + } + + /** + * Determines if the expired bundle in the cache needs + * to be reloaded based on the loading time given by + * loadTime or some other criteria. The method returns + * true if reloading is required; false + * otherwise. loadTime is a millisecond offset since + * the Calendar + * Epoch. + * + * The calling ResourceBundle.getBundle factory method + * calls this method on the ResourceBundle.Control + * instance used for its current invocation, not on the instance + * used in the invocation that originally loaded the resource + * bundle. + * + *

The default implementation compares loadTime and + * the last modified time of the source data of the resource + * bundle. If it's determined that the source data has been modified + * since loadTime, true is + * returned. Otherwise, false is returned. This + * implementation assumes that the given format is the + * same string as its file suffix if it's not one of the default + * formats, "java.class" or + * "java.properties". + * + * @param baseName + * the base bundle name of the resource bundle, a + * fully qualified class name + * @param locale + * the locale for which the resource bundle + * should be instantiated + * @param format + * the resource bundle format to be loaded + * @param loader + * the ClassLoader to use to load the bundle + * @param bundle + * the resource bundle instance that has been expired + * in the cache + * @param loadTime + * the time when bundle was loaded and put + * in the cache + * @return true if the expired bundle needs to be + * reloaded; false otherwise. + * @exception NullPointerException + * if baseName, locale, + * format, loader, or + * bundle is null + */ + public boolean needsReload(String baseName, Locale locale, + String format, ClassLoader loader, + ResourceBundle bundle, long loadTime) { + if (bundle == null) { + throw new NullPointerException(); + } + if (format.equals("java.class") || format.equals("java.properties")) { + format = format.substring(5); + } + boolean result = false; + try { + String resourceName = toResourceName(toBundleName(baseName, locale), format); + URL url = loader.getResource(resourceName); + if (url != null) { + long lastModified = 0; + URLConnection connection = url.openConnection(); + if (connection != null) { + // disable caches to get the correct data + connection.setUseCaches(false); + if (connection instanceof JarURLConnection) { + JarEntry ent = ((JarURLConnection)connection).getJarEntry(); + if (ent != null) { + lastModified = ent.getTime(); + if (lastModified == -1) { + lastModified = 0; + } + } + } else { + lastModified = connection.getLastModified(); + } + } + result = lastModified >= loadTime; + } + } catch (NullPointerException npe) { + throw npe; + } catch (Exception e) { + // ignore other exceptions + } + return result; + } + + /** + * Converts the given baseName and locale + * to the bundle name. This method is called from the default + * implementation of the {@link #newBundle(String, Locale, String, + * ClassLoader, boolean) newBundle} and {@link #needsReload(String, + * Locale, String, ClassLoader, ResourceBundle, long) needsReload} + * methods. + * + *

This implementation returns the following value: + *

+         *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
+         * 
+ * where language, script, country, + * and variant are the language, script, country, and variant + * values of locale, respectively. Final component values that + * are empty Strings are omitted along with the preceding '_'. When the + * script is empty, the script value is ommitted along with the preceding '_'. + * If all of the values are empty strings, then baseName + * is returned. + * + *

For example, if baseName is + * "baseName" and locale is + * Locale("ja", "", "XX"), then + * "baseName_ja_ _XX" is returned. If the given + * locale is Locale("en"), then + * "baseName_en" is returned. + * + *

Overriding this method allows applications to use different + * conventions in the organization and packaging of localized + * resources. + * + * @param baseName + * the base name of the resource bundle, a fully + * qualified class name + * @param locale + * the locale for which a resource bundle should be + * loaded + * @return the bundle name for the resource bundle + * @exception NullPointerException + * if baseName or locale + * is null + */ + public String toBundleName(String baseName, Locale locale) { + if (locale == Locale.ROOT) { + return baseName; + } + + String language = locale.getLanguage(); + String script = locale.getScript(); + String country = locale.getCountry(); + String variant = locale.getVariant(); + + if (language == "" && country == "" && variant == "") { + return baseName; + } + + StringBuilder sb = new StringBuilder(baseName); + sb.append('_'); + if (script != "") { + if (variant != "") { + sb.append(language).append('_').append(script).append('_').append(country).append('_').append(variant); + } else if (country != "") { + sb.append(language).append('_').append(script).append('_').append(country); + } else { + sb.append(language).append('_').append(script); + } + } else { + if (variant != "") { + sb.append(language).append('_').append(country).append('_').append(variant); + } else if (country != "") { + sb.append(language).append('_').append(country); + } else { + sb.append(language); + } + } + return sb.toString(); + + } + + /** + * Converts the given bundleName to the form required + * by the {@link ClassLoader#getResource ClassLoader.getResource} + * method by replacing all occurrences of '.' in + * bundleName with '/' and appending a + * '.' and the given file suffix. For + * example, if bundleName is + * "foo.bar.MyResources_ja_JP" and suffix + * is "properties", then + * "foo/bar/MyResources_ja_JP.properties" is returned. + * + * @param bundleName + * the bundle name + * @param suffix + * the file type suffix + * @return the converted resource name + * @exception NullPointerException + * if bundleName or suffix + * is null + */ + public final String toResourceName(String bundleName, String suffix) { + StringBuilder sb = new StringBuilder(bundleName.length() + 1 + suffix.length()); + sb.append(bundleName.replace('.', '/')).append('.').append(suffix); + return sb.toString(); + } + } + + private static class SingleFormatControl extends Control { + private static final Control PROPERTIES_ONLY + = new SingleFormatControl(FORMAT_PROPERTIES); + + private static final Control CLASS_ONLY + = new SingleFormatControl(FORMAT_CLASS); + + private final List formats; + + protected SingleFormatControl(List formats) { + this.formats = formats; + } + + public List getFormats(String baseName) { + if (baseName == null) { + throw new NullPointerException(); + } + return formats; + } + } + + private static final class NoFallbackControl extends SingleFormatControl { + private static final Control NO_FALLBACK + = new NoFallbackControl(FORMAT_DEFAULT); + + private static final Control PROPERTIES_ONLY_NO_FALLBACK + = new NoFallbackControl(FORMAT_PROPERTIES); + + private static final Control CLASS_ONLY_NO_FALLBACK + = new NoFallbackControl(FORMAT_CLASS); + + protected NoFallbackControl(List formats) { + super(formats); + } + + public Locale getFallbackLocale(String baseName, Locale locale) { + if (baseName == null || locale == null) { + throw new NullPointerException(); + } + return null; + } + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/SimpleTimeZone.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/SimpleTimeZone.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,1706 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.IOException; +import sun.util.calendar.CalendarSystem; +import sun.util.calendar.CalendarUtils; +import sun.util.calendar.BaseCalendar; +import sun.util.calendar.Gregorian; + +/** + * SimpleTimeZone is a concrete subclass of TimeZone + * that represents a time zone for use with a Gregorian calendar. + * The class holds an offset from GMT, called raw offset, and start + * and end rules for a daylight saving time schedule. Since it only holds + * single values for each, it cannot handle historical changes in the offset + * from GMT and the daylight saving schedule, except that the {@link + * #setStartYear setStartYear} method can specify the year when the daylight + * saving time schedule starts in effect. + *

+ * To construct a SimpleTimeZone with a daylight saving time + * schedule, the schedule can be described with a set of rules, + * start-rule and end-rule. A day when daylight saving time + * starts or ends is specified by a combination of month, + * day-of-month, and day-of-week values. The month + * value is represented by a Calendar {@link Calendar#MONTH MONTH} field + * value, such as {@link Calendar#MARCH}. The day-of-week value is + * represented by a Calendar {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} value, + * such as {@link Calendar#SUNDAY SUNDAY}. The meanings of value combinations + * are as follows. + * + *

    + *
  • Exact day of month
    + * To specify an exact day of month, set the month and + * day-of-month to an exact value, and day-of-week to zero. For + * example, to specify March 1, set the month to {@link Calendar#MARCH + * MARCH}, day-of-month to 1, and day-of-week to 0.
  • + * + *
  • Day of week on or after day of month
    + * To specify a day of week on or after an exact day of month, set the + * month to an exact month value, day-of-month to the day on + * or after which the rule is applied, and day-of-week to a negative {@link + * Calendar#DAY_OF_WEEK DAY_OF_WEEK} field value. For example, to specify the + * second Sunday of April, set month to {@link Calendar#APRIL APRIL}, + * day-of-month to 8, and day-of-week to -{@link + * Calendar#SUNDAY SUNDAY}.
  • + * + *
  • Day of week on or before day of month
    + * To specify a day of the week on or before an exact day of the month, set + * day-of-month and day-of-week to a negative value. For + * example, to specify the last Wednesday on or before the 21st of March, set + * month to {@link Calendar#MARCH MARCH}, day-of-month is -21 + * and day-of-week is -{@link Calendar#WEDNESDAY WEDNESDAY}.
  • + * + *
  • Last day-of-week of month
    + * To specify, the last day-of-week of the month, set day-of-week to a + * {@link Calendar#DAY_OF_WEEK DAY_OF_WEEK} value and day-of-month to + * -1. For example, to specify the last Sunday of October, set month + * to {@link Calendar#OCTOBER OCTOBER}, day-of-week to {@link + * Calendar#SUNDAY SUNDAY} and day-of-month to -1.
  • + * + *
+ * The time of the day at which daylight saving time starts or ends is + * specified by a millisecond value within the day. There are three kinds of + * modes to specify the time: {@link #WALL_TIME}, {@link + * #STANDARD_TIME} and {@link #UTC_TIME}. For example, if daylight + * saving time ends + * at 2:00 am in the wall clock time, it can be specified by 7200000 + * milliseconds in the {@link #WALL_TIME} mode. In this case, the wall clock time + * for an end-rule means the same thing as the daylight time. + *

+ * The following are examples of parameters for constructing time zone objects. + *


+ *      // Base GMT offset: -8:00
+ *      // DST starts:      at 2:00am in standard time
+ *      //                  on the first Sunday in April
+ *      // DST ends:        at 2:00am in daylight time
+ *      //                  on the last Sunday in October
+ *      // Save:            1 hour
+ *      SimpleTimeZone(-28800000,
+ *                     "America/Los_Angeles",
+ *                     Calendar.APRIL, 1, -Calendar.SUNDAY,
+ *                     7200000,
+ *                     Calendar.OCTOBER, -1, Calendar.SUNDAY,
+ *                     7200000,
+ *                     3600000)
+ *
+ *      // Base GMT offset: +1:00
+ *      // DST starts:      at 1:00am in UTC time
+ *      //                  on the last Sunday in March
+ *      // DST ends:        at 1:00am in UTC time
+ *      //                  on the last Sunday in October
+ *      // Save:            1 hour
+ *      SimpleTimeZone(3600000,
+ *                     "Europe/Paris",
+ *                     Calendar.MARCH, -1, Calendar.SUNDAY,
+ *                     3600000, SimpleTimeZone.UTC_TIME,
+ *                     Calendar.OCTOBER, -1, Calendar.SUNDAY,
+ *                     3600000, SimpleTimeZone.UTC_TIME,
+ *                     3600000)
+ * 
+ * These parameter rules are also applicable to the set rule methods, such as + * setStartRule. + * + * @since 1.1 + * @see Calendar + * @see GregorianCalendar + * @see TimeZone + * @author David Goldsmith, Mark Davis, Chen-Lieh Huang, Alan Liu + */ + +public class SimpleTimeZone extends TimeZone { + /** + * Constructs a SimpleTimeZone with the given base time zone offset from GMT + * and time zone ID with no daylight saving time schedule. + * + * @param rawOffset The base time zone offset in milliseconds to GMT. + * @param ID The time zone name that is given to this instance. + */ + public SimpleTimeZone(int rawOffset, String ID) + { + this.rawOffset = rawOffset; + setID (ID); + dstSavings = millisPerHour; // In case user sets rules later + } + + /** + * Constructs a SimpleTimeZone with the given base time zone offset from + * GMT, time zone ID, and rules for starting and ending the daylight + * time. + * Both startTime and endTime are specified to be + * represented in the wall clock time. The amount of daylight saving is + * assumed to be 3600000 milliseconds (i.e., one hour). This constructor is + * equivalent to: + *

+     *     SimpleTimeZone(rawOffset,
+     *                    ID,
+     *                    startMonth,
+     *                    startDay,
+     *                    startDayOfWeek,
+     *                    startTime,
+     *                    SimpleTimeZone.{@link #WALL_TIME},
+     *                    endMonth,
+     *                    endDay,
+     *                    endDayOfWeek,
+     *                    endTime,
+     *                    SimpleTimeZone.{@link #WALL_TIME},
+     *                    3600000)
+     * 
+ * + * @param rawOffset The given base time zone offset from GMT. + * @param ID The time zone ID which is given to this object. + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field value (0-based. e.g., 0 + * for January). + * @param startDay The day of the month on which the daylight saving time starts. + * See the class description for the special cases of this parameter. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * See the class description for the special cases of this parameter. + * @param startTime The daylight saving time starting time in local wall clock + * time (in milliseconds within the day), which is local + * standard time in this case. + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * See the class description for the special cases of this parameter. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * See the class description for the special cases of this parameter. + * @param endTime The daylight saving ending time in local wall clock time, + * (in milliseconds within the day) which is local daylight + * time in this case. + * @exception IllegalArgumentException if the month, day, dayOfWeek, or time + * parameters are out of range for the start or end rule + */ + public SimpleTimeZone(int rawOffset, String ID, + int startMonth, int startDay, int startDayOfWeek, int startTime, + int endMonth, int endDay, int endDayOfWeek, int endTime) + { + this(rawOffset, ID, + startMonth, startDay, startDayOfWeek, startTime, WALL_TIME, + endMonth, endDay, endDayOfWeek, endTime, WALL_TIME, + millisPerHour); + } + + /** + * Constructs a SimpleTimeZone with the given base time zone offset from + * GMT, time zone ID, and rules for starting and ending the daylight + * time. + * Both startTime and endTime are assumed to be + * represented in the wall clock time. This constructor is equivalent to: + *

+     *     SimpleTimeZone(rawOffset,
+     *                    ID,
+     *                    startMonth,
+     *                    startDay,
+     *                    startDayOfWeek,
+     *                    startTime,
+     *                    SimpleTimeZone.{@link #WALL_TIME},
+     *                    endMonth,
+     *                    endDay,
+     *                    endDayOfWeek,
+     *                    endTime,
+     *                    SimpleTimeZone.{@link #WALL_TIME},
+     *                    dstSavings)
+     * 
+ * + * @param rawOffset The given base time zone offset from GMT. + * @param ID The time zone ID which is given to this object. + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * See the class description for the special cases of this parameter. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * See the class description for the special cases of this parameter. + * @param startTime The daylight saving time starting time in local wall clock + * time, which is local standard time in this case. + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * See the class description for the special cases of this parameter. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * See the class description for the special cases of this parameter. + * @param endTime The daylight saving ending time in local wall clock time, + * which is local daylight time in this case. + * @param dstSavings The amount of time in milliseconds saved during + * daylight saving time. + * @exception IllegalArgumentException if the month, day, dayOfWeek, or time + * parameters are out of range for the start or end rule + * @since 1.2 + */ + public SimpleTimeZone(int rawOffset, String ID, + int startMonth, int startDay, int startDayOfWeek, int startTime, + int endMonth, int endDay, int endDayOfWeek, int endTime, + int dstSavings) + { + this(rawOffset, ID, + startMonth, startDay, startDayOfWeek, startTime, WALL_TIME, + endMonth, endDay, endDayOfWeek, endTime, WALL_TIME, + dstSavings); + } + + /** + * Constructs a SimpleTimeZone with the given base time zone offset from + * GMT, time zone ID, and rules for starting and ending the daylight + * time. + * This constructor takes the full set of the start and end rules + * parameters, including modes of startTime and + * endTime. The mode specifies either {@link #WALL_TIME wall + * time} or {@link #STANDARD_TIME standard time} or {@link #UTC_TIME UTC + * time}. + * + * @param rawOffset The given base time zone offset from GMT. + * @param ID The time zone ID which is given to this object. + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * See the class description for the special cases of this parameter. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * See the class description for the special cases of this parameter. + * @param startTime The daylight saving time starting time in the time mode + * specified by startTimeMode. + * @param startTimeMode The mode of the start time specified by startTime. + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * See the class description for the special cases of this parameter. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * See the class description for the special cases of this parameter. + * @param endTime The daylight saving ending time in time time mode + * specified by endTimeMode. + * @param endTimeMode The mode of the end time specified by endTime + * @param dstSavings The amount of time in milliseconds saved during + * daylight saving time. + * + * @exception IllegalArgumentException if the month, day, dayOfWeek, time more, or + * time parameters are out of range for the start or end rule, or if a time mode + * value is invalid. + * + * @see #WALL_TIME + * @see #STANDARD_TIME + * @see #UTC_TIME + * + * @since 1.4 + */ + public SimpleTimeZone(int rawOffset, String ID, + int startMonth, int startDay, int startDayOfWeek, + int startTime, int startTimeMode, + int endMonth, int endDay, int endDayOfWeek, + int endTime, int endTimeMode, + int dstSavings) { + + setID(ID); + this.rawOffset = rawOffset; + this.startMonth = startMonth; + this.startDay = startDay; + this.startDayOfWeek = startDayOfWeek; + this.startTime = startTime; + this.startTimeMode = startTimeMode; + this.endMonth = endMonth; + this.endDay = endDay; + this.endDayOfWeek = endDayOfWeek; + this.endTime = endTime; + this.endTimeMode = endTimeMode; + this.dstSavings = dstSavings; + + // this.useDaylight is set by decodeRules + decodeRules(); + if (dstSavings <= 0) { + throw new IllegalArgumentException("Illegal daylight saving value: " + dstSavings); + } + } + + /** + * Sets the daylight saving time starting year. + * + * @param year The daylight saving starting year. + */ + public void setStartYear(int year) + { + startYear = year; + invalidateCache(); + } + + /** + * Sets the daylight saving time start rule. For example, if daylight saving + * time starts on the first Sunday in April at 2 am in local wall clock + * time, you can set the start rule by calling: + *
setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
+ * + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * See the class description for the special cases of this parameter. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * See the class description for the special cases of this parameter. + * @param startTime The daylight saving time starting time in local wall clock + * time, which is local standard time in this case. + * @exception IllegalArgumentException if the startMonth, startDay, + * startDayOfWeek, or startTime parameters are out of range + */ + public void setStartRule(int startMonth, int startDay, int startDayOfWeek, int startTime) + { + this.startMonth = startMonth; + this.startDay = startDay; + this.startDayOfWeek = startDayOfWeek; + this.startTime = startTime; + startTimeMode = WALL_TIME; + decodeStartRule(); + invalidateCache(); + } + + /** + * Sets the daylight saving time start rule to a fixed date within a month. + * This method is equivalent to: + *
setStartRule(startMonth, startDay, 0, startTime)
+ * + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * @param startTime The daylight saving time starting time in local wall clock + * time, which is local standard time in this case. + * See the class description for the special cases of this parameter. + * @exception IllegalArgumentException if the startMonth, + * startDayOfMonth, or startTime parameters are out of range + * @since 1.2 + */ + public void setStartRule(int startMonth, int startDay, int startTime) { + setStartRule(startMonth, startDay, 0, startTime); + } + + /** + * Sets the daylight saving time start rule to a weekday before or after the given date within + * a month, e.g., the first Monday on or after the 8th. + * + * @param startMonth The daylight saving time starting month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 0 for January). + * @param startDay The day of the month on which the daylight saving time starts. + * @param startDayOfWeek The daylight saving time starting day-of-week. + * @param startTime The daylight saving time starting time in local wall clock + * time, which is local standard time in this case. + * @param after If true, this rule selects the first dayOfWeek on or + * after dayOfMonth. If false, this rule + * selects the last dayOfWeek on or before + * dayOfMonth. + * @exception IllegalArgumentException if the startMonth, startDay, + * startDayOfWeek, or startTime parameters are out of range + * @since 1.2 + */ + public void setStartRule(int startMonth, int startDay, int startDayOfWeek, + int startTime, boolean after) + { + // TODO: this method doesn't check the initial values of dayOfMonth or dayOfWeek. + if (after) { + setStartRule(startMonth, startDay, -startDayOfWeek, startTime); + } else { + setStartRule(startMonth, -startDay, -startDayOfWeek, startTime); + } + } + + /** + * Sets the daylight saving time end rule. For example, if daylight saving time + * ends on the last Sunday in October at 2 am in wall clock time, + * you can set the end rule by calling: + * setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000); + * + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * See the class description for the special cases of this parameter. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * See the class description for the special cases of this parameter. + * @param endTime The daylight saving ending time in local wall clock time, + * (in milliseconds within the day) which is local daylight + * time in this case. + * @exception IllegalArgumentException if the endMonth, endDay, + * endDayOfWeek, or endTime parameters are out of range + */ + public void setEndRule(int endMonth, int endDay, int endDayOfWeek, + int endTime) + { + this.endMonth = endMonth; + this.endDay = endDay; + this.endDayOfWeek = endDayOfWeek; + this.endTime = endTime; + this.endTimeMode = WALL_TIME; + decodeEndRule(); + invalidateCache(); + } + + /** + * Sets the daylight saving time end rule to a fixed date within a month. + * This method is equivalent to: + *
setEndRule(endMonth, endDay, 0, endTime)
+ * + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * @param endTime The daylight saving ending time in local wall clock time, + * (in milliseconds within the day) which is local daylight + * time in this case. + * @exception IllegalArgumentException the endMonth, endDay, + * or endTime parameters are out of range + * @since 1.2 + */ + public void setEndRule(int endMonth, int endDay, int endTime) + { + setEndRule(endMonth, endDay, 0, endTime); + } + + /** + * Sets the daylight saving time end rule to a weekday before or after the given date within + * a month, e.g., the first Monday on or after the 8th. + * + * @param endMonth The daylight saving time ending month. Month is + * a {@link Calendar#MONTH MONTH} field + * value (0-based. e.g., 9 for October). + * @param endDay The day of the month on which the daylight saving time ends. + * @param endDayOfWeek The daylight saving time ending day-of-week. + * @param endTime The daylight saving ending time in local wall clock time, + * (in milliseconds within the day) which is local daylight + * time in this case. + * @param after If true, this rule selects the first endDayOfWeek on + * or after endDay. If false, this rule + * selects the last endDayOfWeek on or before + * endDay of the month. + * @exception IllegalArgumentException the endMonth, endDay, + * endDayOfWeek, or endTime parameters are out of range + * @since 1.2 + */ + public void setEndRule(int endMonth, int endDay, int endDayOfWeek, int endTime, boolean after) + { + if (after) { + setEndRule(endMonth, endDay, -endDayOfWeek, endTime); + } else { + setEndRule(endMonth, -endDay, -endDayOfWeek, endTime); + } + } + + /** + * Returns the offset of this time zone from UTC at the given + * time. If daylight saving time is in effect at the given time, + * the offset value is adjusted with the amount of daylight + * saving. + * + * @param date the time at which the time zone offset is found + * @return the amount of time in milliseconds to add to UTC to get + * local time. + * @since 1.4 + */ + public int getOffset(long date) { + return getOffsets(date, null); + } + + /** + * @see TimeZone#getOffsets + */ + int getOffsets(long date, int[] offsets) { + int offset = rawOffset; + + computeOffset: + if (useDaylight) { + synchronized (this) { + if (cacheStart != 0) { + if (date >= cacheStart && date < cacheEnd) { + offset += dstSavings; + break computeOffset; + } + } + } + BaseCalendar cal = date >= GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER ? + gcal : (BaseCalendar) CalendarSystem.forName("julian"); + BaseCalendar.Date cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.NO_TIMEZONE); + // Get the year in local time + cal.getCalendarDate(date + rawOffset, cdate); + int year = cdate.getNormalizedYear(); + if (year >= startYear) { + // Clear time elements for the transition calculations + cdate.setTimeOfDay(0, 0, 0, 0); + offset = getOffset(cal, cdate, year, date); + } + } + + if (offsets != null) { + offsets[0] = rawOffset; + offsets[1] = offset - rawOffset; + } + return offset; + } + + /** + * Returns the difference in milliseconds between local time and + * UTC, taking into account both the raw offset and the effect of + * daylight saving, for the specified date and time. This method + * assumes that the start and end month are distinct. It also + * uses a default {@link GregorianCalendar} object as its + * underlying calendar, such as for determining leap years. Do + * not use the result of this method with a calendar other than a + * default GregorianCalendar. + * + *

Note: In general, clients should use + * Calendar.get(ZONE_OFFSET) + Calendar.get(DST_OFFSET) + * instead of calling this method. + * + * @param era The era of the given date. + * @param year The year in the given date. + * @param month The month in the given date. Month is 0-based. e.g., + * 0 for January. + * @param day The day-in-month of the given date. + * @param dayOfWeek The day-of-week of the given date. + * @param millis The milliseconds in day in standard local time. + * @return The milliseconds to add to UTC to get local time. + * @exception IllegalArgumentException the era, + * month, day, dayOfWeek, + * or millis parameters are out of range + */ + public int getOffset(int era, int year, int month, int day, int dayOfWeek, + int millis) + { + if (era != GregorianCalendar.AD && era != GregorianCalendar.BC) { + throw new IllegalArgumentException("Illegal era " + era); + } + + int y = year; + if (era == GregorianCalendar.BC) { + // adjust y with the GregorianCalendar-style year numbering. + y = 1 - y; + } + + // If the year isn't representable with the 64-bit long + // integer in milliseconds, convert the year to an + // equivalent year. This is required to pass some JCK test cases + // which are actually useless though because the specified years + // can't be supported by the Java time system. + if (y >= 292278994) { + y = 2800 + y % 2800; + } else if (y <= -292269054) { + // y %= 28 also produces an equivalent year, but positive + // year numbers would be convenient to use the UNIX cal + // command. + y = (int) CalendarUtils.mod((long) y, 28); + } + + // convert year to its 1-based month value + int m = month + 1; + + // First, calculate time as a Gregorian date. + BaseCalendar cal = gcal; + BaseCalendar.Date cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.NO_TIMEZONE); + cdate.setDate(y, m, day); + long time = cal.getTime(cdate); // normalize cdate + time += millis - rawOffset; // UTC time + + // If the time value represents a time before the default + // Gregorian cutover, recalculate time using the Julian + // calendar system. For the Julian calendar system, the + // normalized year numbering is ..., -2 (BCE 2), -1 (BCE 1), + // 1, 2 ... which is different from the GregorianCalendar + // style year numbering (..., -1, 0 (BCE 1), 1, 2, ...). + if (time < GregorianCalendar.DEFAULT_GREGORIAN_CUTOVER) { + cal = (BaseCalendar) CalendarSystem.forName("julian"); + cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.NO_TIMEZONE); + cdate.setNormalizedDate(y, m, day); + time = cal.getTime(cdate) + millis - rawOffset; + } + + if ((cdate.getNormalizedYear() != y) + || (cdate.getMonth() != m) + || (cdate.getDayOfMonth() != day) + // The validation should be cdate.getDayOfWeek() == + // dayOfWeek. However, we don't check dayOfWeek for + // compatibility. + || (dayOfWeek < Calendar.SUNDAY || dayOfWeek > Calendar.SATURDAY) + || (millis < 0 || millis >= (24*60*60*1000))) { + throw new IllegalArgumentException(); + } + + if (!useDaylight || year < startYear || era != GregorianCalendar.CE) { + return rawOffset; + } + + return getOffset(cal, cdate, y, time); + } + + private int getOffset(BaseCalendar cal, BaseCalendar.Date cdate, int year, long time) { + synchronized (this) { + if (cacheStart != 0) { + if (time >= cacheStart && time < cacheEnd) { + return rawOffset + dstSavings; + } + if (year == cacheYear) { + return rawOffset; + } + } + } + + long start = getStart(cal, cdate, year); + long end = getEnd(cal, cdate, year); + int offset = rawOffset; + if (start <= end) { + if (time >= start && time < end) { + offset += dstSavings; + } + synchronized (this) { + cacheYear = year; + cacheStart = start; + cacheEnd = end; + } + } else { + if (time < end) { + // TODO: support Gregorian cutover. The previous year + // may be in the other calendar system. + start = getStart(cal, cdate, year - 1); + if (time >= start) { + offset += dstSavings; + } + } else if (time >= start) { + // TODO: support Gregorian cutover. The next year + // may be in the other calendar system. + end = getEnd(cal, cdate, year + 1); + if (time < end) { + offset += dstSavings; + } + } + if (start <= end) { + synchronized (this) { + // The start and end transitions are in multiple years. + cacheYear = (long) startYear - 1; + cacheStart = start; + cacheEnd = end; + } + } + } + return offset; + } + + private long getStart(BaseCalendar cal, BaseCalendar.Date cdate, int year) { + int time = startTime; + if (startTimeMode != UTC_TIME) { + time -= rawOffset; + } + return getTransition(cal, cdate, startMode, year, startMonth, startDay, + startDayOfWeek, time); + } + + private long getEnd(BaseCalendar cal, BaseCalendar.Date cdate, int year) { + int time = endTime; + if (endTimeMode != UTC_TIME) { + time -= rawOffset; + } + if (endTimeMode == WALL_TIME) { + time -= dstSavings; + } + return getTransition(cal, cdate, endMode, year, endMonth, endDay, + endDayOfWeek, time); + } + + private long getTransition(BaseCalendar cal, BaseCalendar.Date cdate, + int mode, int year, int month, int dayOfMonth, + int dayOfWeek, int timeOfDay) { + cdate.setNormalizedYear(year); + cdate.setMonth(month + 1); + switch (mode) { + case DOM_MODE: + cdate.setDayOfMonth(dayOfMonth); + break; + + case DOW_IN_MONTH_MODE: + cdate.setDayOfMonth(1); + if (dayOfMonth < 0) { + cdate.setDayOfMonth(cal.getMonthLength(cdate)); + } + cdate = (BaseCalendar.Date) cal.getNthDayOfWeek(dayOfMonth, dayOfWeek, cdate); + break; + + case DOW_GE_DOM_MODE: + cdate.setDayOfMonth(dayOfMonth); + cdate = (BaseCalendar.Date) cal.getNthDayOfWeek(1, dayOfWeek, cdate); + break; + + case DOW_LE_DOM_MODE: + cdate.setDayOfMonth(dayOfMonth); + cdate = (BaseCalendar.Date) cal.getNthDayOfWeek(-1, dayOfWeek, cdate); + break; + } + return cal.getTime(cdate) + timeOfDay; + } + + /** + * Gets the GMT offset for this time zone. + * @return the GMT offset value in milliseconds + * @see #setRawOffset + */ + public int getRawOffset() + { + // The given date will be taken into account while + // we have the historical time zone data in place. + return rawOffset; + } + + /** + * Sets the base time zone offset to GMT. + * This is the offset to add to UTC to get local time. + * @see #getRawOffset + */ + public void setRawOffset(int offsetMillis) + { + this.rawOffset = offsetMillis; + } + + /** + * Sets the amount of time in milliseconds that the clock is advanced + * during daylight saving time. + * @param millisSavedDuringDST the number of milliseconds the time is + * advanced with respect to standard time when the daylight saving time rules + * are in effect. A positive number, typically one hour (3600000). + * @see #getDSTSavings + * @since 1.2 + */ + public void setDSTSavings(int millisSavedDuringDST) { + if (millisSavedDuringDST <= 0) { + throw new IllegalArgumentException("Illegal daylight saving value: " + + millisSavedDuringDST); + } + dstSavings = millisSavedDuringDST; + } + + /** + * Returns the amount of time in milliseconds that the clock is + * advanced during daylight saving time. + * + * @return the number of milliseconds the time is advanced with + * respect to standard time when the daylight saving rules are in + * effect, or 0 (zero) if this time zone doesn't observe daylight + * saving time. + * + * @see #setDSTSavings + * @since 1.2 + */ + public int getDSTSavings() { + return useDaylight ? dstSavings : 0; + } + + /** + * Queries if this time zone uses daylight saving time. + * @return true if this time zone uses daylight saving time; + * false otherwise. + */ + public boolean useDaylightTime() + { + return useDaylight; + } + + /** + * Returns {@code true} if this {@code SimpleTimeZone} observes + * Daylight Saving Time. This method is equivalent to {@link + * #useDaylightTime()}. + * + * @return {@code true} if this {@code SimpleTimeZone} observes + * Daylight Saving Time; {@code false} otherwise. + * @since 1.7 + */ + @Override + public boolean observesDaylightTime() { + return useDaylightTime(); + } + + /** + * Queries if the given date is in daylight saving time. + * @return true if daylight saving time is in effective at the + * given date; false otherwise. + */ + public boolean inDaylightTime(Date date) + { + return (getOffset(date.getTime()) != rawOffset); + } + + /** + * Returns a clone of this SimpleTimeZone instance. + * @return a clone of this instance. + */ + public Object clone() + { + return super.clone(); + } + + /** + * Generates the hash code for the SimpleDateFormat object. + * @return the hash code for this object + */ + public synchronized int hashCode() + { + return startMonth ^ startDay ^ startDayOfWeek ^ startTime ^ + endMonth ^ endDay ^ endDayOfWeek ^ endTime ^ rawOffset; + } + + /** + * Compares the equality of two SimpleTimeZone objects. + * + * @param obj The SimpleTimeZone object to be compared with. + * @return True if the given obj is the same as this + * SimpleTimeZone object; false otherwise. + */ + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if (!(obj instanceof SimpleTimeZone)) { + return false; + } + + SimpleTimeZone that = (SimpleTimeZone) obj; + + return getID().equals(that.getID()) && + hasSameRules(that); + } + + /** + * Returns true if this zone has the same rules and offset as another zone. + * @param other the TimeZone object to be compared with + * @return true if the given zone is a SimpleTimeZone and has the + * same rules and offset as this one + * @since 1.2 + */ + public boolean hasSameRules(TimeZone other) { + if (this == other) { + return true; + } + if (!(other instanceof SimpleTimeZone)) { + return false; + } + SimpleTimeZone that = (SimpleTimeZone) other; + return rawOffset == that.rawOffset && + useDaylight == that.useDaylight && + (!useDaylight + // Only check rules if using DST + || (dstSavings == that.dstSavings && + startMode == that.startMode && + startMonth == that.startMonth && + startDay == that.startDay && + startDayOfWeek == that.startDayOfWeek && + startTime == that.startTime && + startTimeMode == that.startTimeMode && + endMode == that.endMode && + endMonth == that.endMonth && + endDay == that.endDay && + endDayOfWeek == that.endDayOfWeek && + endTime == that.endTime && + endTimeMode == that.endTimeMode && + startYear == that.startYear)); + } + + /** + * Returns a string representation of this time zone. + * @return a string representation of this time zone. + */ + public String toString() { + return getClass().getName() + + "[id=" + getID() + + ",offset=" + rawOffset + + ",dstSavings=" + dstSavings + + ",useDaylight=" + useDaylight + + ",startYear=" + startYear + + ",startMode=" + startMode + + ",startMonth=" + startMonth + + ",startDay=" + startDay + + ",startDayOfWeek=" + startDayOfWeek + + ",startTime=" + startTime + + ",startTimeMode=" + startTimeMode + + ",endMode=" + endMode + + ",endMonth=" + endMonth + + ",endDay=" + endDay + + ",endDayOfWeek=" + endDayOfWeek + + ",endTime=" + endTime + + ",endTimeMode=" + endTimeMode + ']'; + } + + // =======================privates=============================== + + /** + * The month in which daylight saving time starts. This value must be + * between Calendar.JANUARY and + * Calendar.DECEMBER inclusive. This value must not equal + * endMonth. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int startMonth; + + /** + * This field has two possible interpretations: + *

+ *
startMode == DOW_IN_MONTH
+ *
+ * startDay indicates the day of the month of + * startMonth on which daylight + * saving time starts, from 1 to 28, 30, or 31, depending on the + * startMonth. + *
+ *
startMode != DOW_IN_MONTH
+ *
+ * startDay indicates which startDayOfWeek in the + * month startMonth daylight + * saving time starts on. For example, a value of +1 and a + * startDayOfWeek of Calendar.SUNDAY indicates the + * first Sunday of startMonth. Likewise, +2 would indicate the + * second Sunday, and -1 the last Sunday. A value of 0 is illegal. + *
+ *
+ *

If useDaylight is false, this value is ignored. + * @serial + */ + private int startDay; + + /** + * The day of the week on which daylight saving time starts. This value + * must be between Calendar.SUNDAY and + * Calendar.SATURDAY inclusive. + *

If useDaylight is false or + * startMode == DAY_OF_MONTH, this value is ignored. + * @serial + */ + private int startDayOfWeek; + + /** + * The time in milliseconds after midnight at which daylight saving + * time starts. This value is expressed as wall time, standard time, + * or UTC time, depending on the setting of startTimeMode. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int startTime; + + /** + * The format of startTime, either WALL_TIME, STANDARD_TIME, or UTC_TIME. + * @serial + * @since 1.3 + */ + private int startTimeMode; + + /** + * The month in which daylight saving time ends. This value must be + * between Calendar.JANUARY and + * Calendar.UNDECIMBER. This value must not equal + * startMonth. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int endMonth; + + /** + * This field has two possible interpretations: + *

+ *
endMode == DOW_IN_MONTH
+ *
+ * endDay indicates the day of the month of + * endMonth on which daylight + * saving time ends, from 1 to 28, 30, or 31, depending on the + * endMonth. + *
+ *
endMode != DOW_IN_MONTH
+ *
+ * endDay indicates which endDayOfWeek in th + * month endMonth daylight + * saving time ends on. For example, a value of +1 and a + * endDayOfWeek of Calendar.SUNDAY indicates the + * first Sunday of endMonth. Likewise, +2 would indicate the + * second Sunday, and -1 the last Sunday. A value of 0 is illegal. + *
+ *
+ *

If useDaylight is false, this value is ignored. + * @serial + */ + private int endDay; + + /** + * The day of the week on which daylight saving time ends. This value + * must be between Calendar.SUNDAY and + * Calendar.SATURDAY inclusive. + *

If useDaylight is false or + * endMode == DAY_OF_MONTH, this value is ignored. + * @serial + */ + private int endDayOfWeek; + + /** + * The time in milliseconds after midnight at which daylight saving + * time ends. This value is expressed as wall time, standard time, + * or UTC time, depending on the setting of endTimeMode. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int endTime; + + /** + * The format of endTime, either WALL_TIME, + * STANDARD_TIME, or UTC_TIME. + * @serial + * @since 1.3 + */ + private int endTimeMode; + + /** + * The year in which daylight saving time is first observed. This is an {@link GregorianCalendar#AD AD} + * value. If this value is less than 1 then daylight saving time is observed + * for all AD years. + *

If useDaylight is false, this value is ignored. + * @serial + */ + private int startYear; + + /** + * The offset in milliseconds between this zone and GMT. Negative offsets + * are to the west of Greenwich. To obtain local standard time, + * add the offset to GMT time. To obtain local wall time it may also be + * necessary to add dstSavings. + * @serial + */ + private int rawOffset; + + /** + * A boolean value which is true if and only if this zone uses daylight + * saving time. If this value is false, several other fields are ignored. + * @serial + */ + private boolean useDaylight=false; // indicate if this time zone uses DST + + private static final int millisPerHour = 60*60*1000; + private static final int millisPerDay = 24*millisPerHour; + + /** + * This field was serialized in JDK 1.1, so we have to keep it that way + * to maintain serialization compatibility. However, there's no need to + * recreate the array each time we create a new time zone. + * @serial An array of bytes containing the values {31, 28, 31, 30, 31, 30, + * 31, 31, 30, 31, 30, 31}. This is ignored as of the Java 2 platform v1.2, however, it must + * be streamed out for compatibility with JDK 1.1. + */ + private final byte monthLength[] = staticMonthLength; + private final static byte staticMonthLength[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + private final static byte staticLeapMonthLength[] = {31,29,31,30,31,30,31,31,30,31,30,31}; + + /** + * Variables specifying the mode of the start rule. Takes the following + * values: + *

+ *
DOM_MODE
+ *
+ * Exact day of week; e.g., March 1. + *
+ *
DOW_IN_MONTH_MODE
+ *
+ * Day of week in month; e.g., last Sunday in March. + *
+ *
DOW_GE_DOM_MODE
+ *
+ * Day of week after day of month; e.g., Sunday on or after March 15. + *
+ *
DOW_LE_DOM_MODE
+ *
+ * Day of week before day of month; e.g., Sunday on or before March 15. + *
+ *
+ * The setting of this field affects the interpretation of the + * startDay field. + *

If useDaylight is false, this value is ignored. + * @serial + * @since 1.1.4 + */ + private int startMode; + + /** + * Variables specifying the mode of the end rule. Takes the following + * values: + *

+ *
DOM_MODE
+ *
+ * Exact day of week; e.g., March 1. + *
+ *
DOW_IN_MONTH_MODE
+ *
+ * Day of week in month; e.g., last Sunday in March. + *
+ *
DOW_GE_DOM_MODE
+ *
+ * Day of week after day of month; e.g., Sunday on or after March 15. + *
+ *
DOW_LE_DOM_MODE
+ *
+ * Day of week before day of month; e.g., Sunday on or before March 15. + *
+ *
+ * The setting of this field affects the interpretation of the + * endDay field. + *

If useDaylight is false, this value is ignored. + * @serial + * @since 1.1.4 + */ + private int endMode; + + /** + * A positive value indicating the amount of time saved during DST in + * milliseconds. + * Typically one hour (3600000); sometimes 30 minutes (1800000). + *

If useDaylight is false, this value is ignored. + * @serial + * @since 1.1.4 + */ + private int dstSavings; + + private static final Gregorian gcal = CalendarSystem.getGregorianCalendar(); + + /** + * Cache values representing a single period of daylight saving + * time. When the cache values are valid, cacheStart is the start + * time (inclusive) of daylight saving time and cacheEnd is the + * end time (exclusive). + * + * cacheYear has a year value if both cacheStart and cacheEnd are + * in the same year. cacheYear is set to startYear - 1 if + * cacheStart and cacheEnd are in different years. cacheStart is 0 + * if the cache values are void. cacheYear is a long to support + * Integer.MIN_VALUE - 1 (JCK requirement). + */ + private transient long cacheYear; + private transient long cacheStart; + private transient long cacheEnd; + + /** + * Constants specifying values of startMode and endMode. + */ + private static final int DOM_MODE = 1; // Exact day of month, "Mar 1" + private static final int DOW_IN_MONTH_MODE = 2; // Day of week in month, "lastSun" + private static final int DOW_GE_DOM_MODE = 3; // Day of week after day of month, "Sun>=15" + private static final int DOW_LE_DOM_MODE = 4; // Day of week before day of month, "Sun<=21" + + /** + * Constant for a mode of start or end time specified as wall clock + * time. Wall clock time is standard time for the onset rule, and + * daylight time for the end rule. + * @since 1.4 + */ + public static final int WALL_TIME = 0; // Zero for backward compatibility + + /** + * Constant for a mode of start or end time specified as standard time. + * @since 1.4 + */ + public static final int STANDARD_TIME = 1; + + /** + * Constant for a mode of start or end time specified as UTC. European + * Union rules are specified as UTC time, for example. + * @since 1.4 + */ + public static final int UTC_TIME = 2; + + // Proclaim compatibility with 1.1 + static final long serialVersionUID = -403250971215465050L; + + // the internal serial version which says which version was written + // - 0 (default) for version up to JDK 1.1.3 + // - 1 for version from JDK 1.1.4, which includes 3 new fields + // - 2 for JDK 1.3, which includes 2 new fields + static final int currentSerialVersion = 2; + + /** + * The version of the serialized data on the stream. Possible values: + *

+ *
0 or not present on stream
+ *
+ * JDK 1.1.3 or earlier. + *
+ *
1
+ *
+ * JDK 1.1.4 or later. Includes three new fields: startMode, + * endMode, and dstSavings. + *
+ *
2
+ *
+ * JDK 1.3 or later. Includes two new fields: startTimeMode + * and endTimeMode. + *
+ *
+ * When streaming out this class, the most recent format + * and the highest allowable serialVersionOnStream + * is written. + * @serial + * @since 1.1.4 + */ + private int serialVersionOnStream = currentSerialVersion; + + synchronized private void invalidateCache() { + cacheYear = startYear - 1; + cacheStart = cacheEnd = 0; + } + + //---------------------------------------------------------------------- + // Rule representation + // + // We represent the following flavors of rules: + // 5 the fifth of the month + // lastSun the last Sunday in the month + // lastMon the last Monday in the month + // Sun>=8 first Sunday on or after the eighth + // Sun<=25 last Sunday on or before the 25th + // This is further complicated by the fact that we need to remain + // backward compatible with the 1.1 FCS. Finally, we need to minimize + // API changes. In order to satisfy these requirements, we support + // three representation systems, and we translate between them. + // + // INTERNAL REPRESENTATION + // This is the format SimpleTimeZone objects take after construction or + // streaming in is complete. Rules are represented directly, using an + // unencoded format. We will discuss the start rule only below; the end + // rule is analogous. + // startMode Takes on enumerated values DAY_OF_MONTH, + // DOW_IN_MONTH, DOW_AFTER_DOM, or DOW_BEFORE_DOM. + // startDay The day of the month, or for DOW_IN_MONTH mode, a + // value indicating which DOW, such as +1 for first, + // +2 for second, -1 for last, etc. + // startDayOfWeek The day of the week. Ignored for DAY_OF_MONTH. + // + // ENCODED REPRESENTATION + // This is the format accepted by the constructor and by setStartRule() + // and setEndRule(). It uses various combinations of positive, negative, + // and zero values to encode the different rules. This representation + // allows us to specify all the different rule flavors without altering + // the API. + // MODE startMonth startDay startDayOfWeek + // DOW_IN_MONTH_MODE >=0 !=0 >0 + // DOM_MODE >=0 >0 ==0 + // DOW_GE_DOM_MODE >=0 >0 <0 + // DOW_LE_DOM_MODE >=0 <0 <0 + // (no DST) don't care ==0 don't care + // + // STREAMED REPRESENTATION + // We must retain binary compatibility with the 1.1 FCS. The 1.1 code only + // handles DOW_IN_MONTH_MODE and non-DST mode, the latter indicated by the + // flag useDaylight. When we stream an object out, we translate into an + // approximate DOW_IN_MONTH_MODE representation so the object can be parsed + // and used by 1.1 code. Following that, we write out the full + // representation separately so that contemporary code can recognize and + // parse it. The full representation is written in a "packed" format, + // consisting of a version number, a length, and an array of bytes. Future + // versions of this class may specify different versions. If they wish to + // include additional data, they should do so by storing them after the + // packed representation below. + //---------------------------------------------------------------------- + + /** + * Given a set of encoded rules in startDay and startDayOfMonth, decode + * them and set the startMode appropriately. Do the same for endDay and + * endDayOfMonth. Upon entry, the day of week variables may be zero or + * negative, in order to indicate special modes. The day of month + * variables may also be negative. Upon exit, the mode variables will be + * set, and the day of week and day of month variables will be positive. + * This method also recognizes a startDay or endDay of zero as indicating + * no DST. + */ + private void decodeRules() + { + decodeStartRule(); + decodeEndRule(); + } + + /** + * Decode the start rule and validate the parameters. The parameters are + * expected to be in encoded form, which represents the various rule modes + * by negating or zeroing certain values. Representation formats are: + *

+ *

+     *            DOW_IN_MONTH  DOM    DOW>=DOM  DOW<=DOM  no DST
+     *            ------------  -----  --------  --------  ----------
+     * month       0..11        same    same      same     don't care
+     * day        -5..5         1..31   1..31    -1..-31   0
+     * dayOfWeek   1..7         0      -1..-7    -1..-7    don't care
+     * time        0..ONEDAY    same    same      same     don't care
+     * 
+ * The range for month does not include UNDECIMBER since this class is + * really specific to GregorianCalendar, which does not use that month. + * The range for time includes ONEDAY (vs. ending at ONEDAY-1) because the + * end rule is an exclusive limit point. That is, the range of times that + * are in DST include those >= the start and < the end. For this reason, + * it should be possible to specify an end of ONEDAY in order to include the + * entire day. Although this is equivalent to time 0 of the following day, + * it's not always possible to specify that, for example, on December 31. + * While arguably the start range should still be 0..ONEDAY-1, we keep + * the start and end ranges the same for consistency. + */ + private void decodeStartRule() { + useDaylight = (startDay != 0) && (endDay != 0); + if (startDay != 0) { + if (startMonth < Calendar.JANUARY || startMonth > Calendar.DECEMBER) { + throw new IllegalArgumentException( + "Illegal start month " + startMonth); + } + if (startTime < 0 || startTime > millisPerDay) { + throw new IllegalArgumentException( + "Illegal start time " + startTime); + } + if (startDayOfWeek == 0) { + startMode = DOM_MODE; + } else { + if (startDayOfWeek > 0) { + startMode = DOW_IN_MONTH_MODE; + } else { + startDayOfWeek = -startDayOfWeek; + if (startDay > 0) { + startMode = DOW_GE_DOM_MODE; + } else { + startDay = -startDay; + startMode = DOW_LE_DOM_MODE; + } + } + if (startDayOfWeek > Calendar.SATURDAY) { + throw new IllegalArgumentException( + "Illegal start day of week " + startDayOfWeek); + } + } + if (startMode == DOW_IN_MONTH_MODE) { + if (startDay < -5 || startDay > 5) { + throw new IllegalArgumentException( + "Illegal start day of week in month " + startDay); + } + } else if (startDay < 1 || startDay > staticMonthLength[startMonth]) { + throw new IllegalArgumentException( + "Illegal start day " + startDay); + } + } + } + + /** + * Decode the end rule and validate the parameters. This method is exactly + * analogous to decodeStartRule(). + * @see decodeStartRule + */ + private void decodeEndRule() { + useDaylight = (startDay != 0) && (endDay != 0); + if (endDay != 0) { + if (endMonth < Calendar.JANUARY || endMonth > Calendar.DECEMBER) { + throw new IllegalArgumentException( + "Illegal end month " + endMonth); + } + if (endTime < 0 || endTime > millisPerDay) { + throw new IllegalArgumentException( + "Illegal end time " + endTime); + } + if (endDayOfWeek == 0) { + endMode = DOM_MODE; + } else { + if (endDayOfWeek > 0) { + endMode = DOW_IN_MONTH_MODE; + } else { + endDayOfWeek = -endDayOfWeek; + if (endDay > 0) { + endMode = DOW_GE_DOM_MODE; + } else { + endDay = -endDay; + endMode = DOW_LE_DOM_MODE; + } + } + if (endDayOfWeek > Calendar.SATURDAY) { + throw new IllegalArgumentException( + "Illegal end day of week " + endDayOfWeek); + } + } + if (endMode == DOW_IN_MONTH_MODE) { + if (endDay < -5 || endDay > 5) { + throw new IllegalArgumentException( + "Illegal end day of week in month " + endDay); + } + } else if (endDay < 1 || endDay > staticMonthLength[endMonth]) { + throw new IllegalArgumentException( + "Illegal end day " + endDay); + } + } + } + + /** + * Make rules compatible to 1.1 FCS code. Since 1.1 FCS code only understands + * day-of-week-in-month rules, we must modify other modes of rules to their + * approximate equivalent in 1.1 FCS terms. This method is used when streaming + * out objects of this class. After it is called, the rules will be modified, + * with a possible loss of information. startMode and endMode will NOT be + * altered, even though semantically they should be set to DOW_IN_MONTH_MODE, + * since the rule modification is only intended to be temporary. + */ + private void makeRulesCompatible() + { + switch (startMode) { + case DOM_MODE: + startDay = 1 + (startDay / 7); + startDayOfWeek = Calendar.SUNDAY; + break; + + case DOW_GE_DOM_MODE: + // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE + // that is, Sun>=1 == firstSun. + if (startDay != 1) { + startDay = 1 + (startDay / 7); + } + break; + + case DOW_LE_DOM_MODE: + if (startDay >= 30) { + startDay = -1; + } else { + startDay = 1 + (startDay / 7); + } + break; + } + + switch (endMode) { + case DOM_MODE: + endDay = 1 + (endDay / 7); + endDayOfWeek = Calendar.SUNDAY; + break; + + case DOW_GE_DOM_MODE: + // A day-of-month of 1 is equivalent to DOW_IN_MONTH_MODE + // that is, Sun>=1 == firstSun. + if (endDay != 1) { + endDay = 1 + (endDay / 7); + } + break; + + case DOW_LE_DOM_MODE: + if (endDay >= 30) { + endDay = -1; + } else { + endDay = 1 + (endDay / 7); + } + break; + } + + /* + * Adjust the start and end times to wall time. This works perfectly + * well unless it pushes into the next or previous day. If that + * happens, we attempt to adjust the day rule somewhat crudely. The day + * rules have been forced into DOW_IN_MONTH mode already, so we change + * the day of week to move forward or back by a day. It's possible to + * make a more refined adjustment of the original rules first, but in + * most cases this extra effort will go to waste once we adjust the day + * rules anyway. + */ + switch (startTimeMode) { + case UTC_TIME: + startTime += rawOffset; + break; + } + while (startTime < 0) { + startTime += millisPerDay; + startDayOfWeek = 1 + ((startDayOfWeek+5) % 7); // Back 1 day + } + while (startTime >= millisPerDay) { + startTime -= millisPerDay; + startDayOfWeek = 1 + (startDayOfWeek % 7); // Forward 1 day + } + + switch (endTimeMode) { + case UTC_TIME: + endTime += rawOffset + dstSavings; + break; + case STANDARD_TIME: + endTime += dstSavings; + } + while (endTime < 0) { + endTime += millisPerDay; + endDayOfWeek = 1 + ((endDayOfWeek+5) % 7); // Back 1 day + } + while (endTime >= millisPerDay) { + endTime -= millisPerDay; + endDayOfWeek = 1 + (endDayOfWeek % 7); // Forward 1 day + } + } + + /** + * Pack the start and end rules into an array of bytes. Only pack + * data which is not preserved by makeRulesCompatible. + */ + private byte[] packRules() + { + byte[] rules = new byte[6]; + rules[0] = (byte)startDay; + rules[1] = (byte)startDayOfWeek; + rules[2] = (byte)endDay; + rules[3] = (byte)endDayOfWeek; + + // As of serial version 2, include time modes + rules[4] = (byte)startTimeMode; + rules[5] = (byte)endTimeMode; + + return rules; + } + + /** + * Given an array of bytes produced by packRules, interpret them + * as the start and end rules. + */ + private void unpackRules(byte[] rules) + { + startDay = rules[0]; + startDayOfWeek = rules[1]; + endDay = rules[2]; + endDayOfWeek = rules[3]; + + // As of serial version 2, include time modes + if (rules.length >= 6) { + startTimeMode = rules[4]; + endTimeMode = rules[5]; + } + } + + /** + * Pack the start and end times into an array of bytes. This is required + * as of serial version 2. + */ + private int[] packTimes() { + int[] times = new int[2]; + times[0] = startTime; + times[1] = endTime; + return times; + } + + /** + * Unpack the start and end times from an array of bytes. This is required + * as of serial version 2. + */ + private void unpackTimes(int[] times) { + startTime = times[0]; + endTime = times[1]; + } + + /** + * Save the state of this object to a stream (i.e., serialize it). + * + * @serialData We write out two formats, a JDK 1.1 compatible format, using + * DOW_IN_MONTH_MODE rules, in the required section, followed + * by the full rules, in packed format, in the optional section. The + * optional section will be ignored by JDK 1.1 code upon stream in. + *

Contents of the optional section: The length of a byte array is + * emitted (int); this is 4 as of this release. The byte array of the given + * length is emitted. The contents of the byte array are the true values of + * the fields startDay, startDayOfWeek, + * endDay, and endDayOfWeek. The values of these + * fields in the required section are approximate values suited to the rule + * mode DOW_IN_MONTH_MODE, which is the only mode recognized by + * JDK 1.1. + */ + private void writeObject(ObjectOutputStream stream) + throws IOException + { + // Construct a binary rule + byte[] rules = packRules(); + int[] times = packTimes(); + + // Convert to 1.1 FCS rules. This step may cause us to lose information. + makeRulesCompatible(); + + // Write out the 1.1 FCS rules + stream.defaultWriteObject(); + + // Write out the binary rules in the optional data area of the stream. + stream.writeInt(rules.length); + stream.write(rules); + stream.writeObject(times); + + // Recover the original rules. This recovers the information lost + // by makeRulesCompatible. + unpackRules(rules); + unpackTimes(times); + } + + /** + * Reconstitute this object from a stream (i.e., deserialize it). + * + * We handle both JDK 1.1 + * binary formats and full formats with a packed byte array. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + + if (serialVersionOnStream < 1) { + // Fix a bug in the 1.1 SimpleTimeZone code -- namely, + // startDayOfWeek and endDayOfWeek were usually uninitialized. We can't do + // too much, so we assume SUNDAY, which actually works most of the time. + if (startDayOfWeek == 0) { + startDayOfWeek = Calendar.SUNDAY; + } + if (endDayOfWeek == 0) { + endDayOfWeek = Calendar.SUNDAY; + } + + // The variables dstSavings, startMode, and endMode are post-1.1, so they + // won't be present if we're reading from a 1.1 stream. Fix them up. + startMode = endMode = DOW_IN_MONTH_MODE; + dstSavings = millisPerHour; + } else { + // For 1.1.4, in addition to the 3 new instance variables, we also + // store the actual rules (which have not be made compatible with 1.1) + // in the optional area. Read them in here and parse them. + int length = stream.readInt(); + byte[] rules = new byte[length]; + stream.readFully(rules); + unpackRules(rules); + } + + if (serialVersionOnStream >= 2) { + int[] times = (int[]) stream.readObject(); + unpackTimes(times); + } + + serialVersionOnStream = currentSerialVersion; + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/TimeZone.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/TimeZone.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,864 @@ +/* + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.util; + +import java.io.Serializable; +import java.lang.ref.SoftReference; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.ConcurrentHashMap; +import sun.security.action.GetPropertyAction; +import sun.util.TimeZoneNameUtility; +import sun.util.calendar.ZoneInfo; +import sun.util.calendar.ZoneInfoFile; + +/** + * TimeZone represents a time zone offset, and also figures out daylight + * savings. + * + *

+ * Typically, you get a TimeZone using getDefault + * which creates a TimeZone based on the time zone where the program + * is running. For example, for a program running in Japan, getDefault + * creates a TimeZone object based on Japanese Standard Time. + * + *

+ * You can also get a TimeZone using getTimeZone + * along with a time zone ID. For instance, the time zone ID for the + * U.S. Pacific Time zone is "America/Los_Angeles". So, you can get a + * U.S. Pacific Time TimeZone object with: + *

+ * TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
+ * 
+ * You can use the getAvailableIDs method to iterate through + * all the supported time zone IDs. You can then choose a + * supported ID to get a TimeZone. + * If the time zone you want is not represented by one of the + * supported IDs, then a custom time zone ID can be specified to + * produce a TimeZone. The syntax of a custom time zone ID is: + * + *
+ * CustomID:
+ *         GMT Sign Hours : Minutes
+ *         GMT Sign Hours Minutes
+ *         GMT Sign Hours
+ * Sign: one of
+ *         + -
+ * Hours:
+ *         Digit
+ *         Digit Digit
+ * Minutes:
+ *         Digit Digit
+ * Digit: one of
+ *         0 1 2 3 4 5 6 7 8 9
+ * 
+ * + * Hours must be between 0 to 23 and Minutes must be + * between 00 to 59. For example, "GMT+10" and "GMT+0010" mean ten + * hours and ten minutes ahead of GMT, respectively. + *

+ * The format is locale independent and digits must be taken from the + * Basic Latin block of the Unicode standard. No daylight saving time + * transition schedule can be specified with a custom time zone ID. If + * the specified string doesn't match the syntax, "GMT" + * is used. + *

+ * When creating a TimeZone, the specified custom time + * zone ID is normalized in the following syntax: + *

+ * NormalizedCustomID:
+ *         GMT Sign TwoDigitHours : Minutes
+ * Sign: one of
+ *         + -
+ * TwoDigitHours:
+ *         Digit Digit
+ * Minutes:
+ *         Digit Digit
+ * Digit: one of
+ *         0 1 2 3 4 5 6 7 8 9
+ * 
+ * For example, TimeZone.getTimeZone("GMT-8").getID() returns "GMT-08:00". + * + *

Three-letter time zone IDs

+ * + * For compatibility with JDK 1.1.x, some other three-letter time zone IDs + * (such as "PST", "CTT", "AST") are also supported. However, their + * use is deprecated because the same abbreviation is often used + * for multiple time zones (for example, "CST" could be U.S. "Central Standard + * Time" and "China Standard Time"), and the Java platform can then only + * recognize one of them. + * + * + * @see Calendar + * @see GregorianCalendar + * @see SimpleTimeZone + * @author Mark Davis, David Goldsmith, Chen-Lieh Huang, Alan Liu + * @since JDK1.1 + */ +abstract public class TimeZone implements Serializable, Cloneable { + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + public TimeZone() { + } + + /** + * A style specifier for getDisplayName() indicating + * a short name, such as "PST." + * @see #LONG + * @since 1.2 + */ + public static final int SHORT = 0; + + /** + * A style specifier for getDisplayName() indicating + * a long name, such as "Pacific Standard Time." + * @see #SHORT + * @since 1.2 + */ + public static final int LONG = 1; + + // Constants used internally; unit is milliseconds + private static final int ONE_MINUTE = 60*1000; + private static final int ONE_HOUR = 60*ONE_MINUTE; + private static final int ONE_DAY = 24*ONE_HOUR; + + // Proclaim serialization compatibility with JDK 1.1 + static final long serialVersionUID = 3581463369166924961L; + + /** + * Gets the time zone offset, for current date, modified in case of + * daylight savings. This is the offset to add to UTC to get local time. + *

+ * This method returns a historically correct offset if an + * underlying TimeZone implementation subclass + * supports historical Daylight Saving Time schedule and GMT + * offset changes. + * + * @param era the era of the given date. + * @param year the year in the given date. + * @param month the month in the given date. + * Month is 0-based. e.g., 0 for January. + * @param day the day-in-month of the given date. + * @param dayOfWeek the day-of-week of the given date. + * @param milliseconds the milliseconds in day in standard + * local time. + * + * @return the offset in milliseconds to add to GMT to get local time. + * + * @see Calendar#ZONE_OFFSET + * @see Calendar#DST_OFFSET + */ + public abstract int getOffset(int era, int year, int month, int day, + int dayOfWeek, int milliseconds); + + /** + * Returns the offset of this time zone from UTC at the specified + * date. If Daylight Saving Time is in effect at the specified + * date, the offset value is adjusted with the amount of daylight + * saving. + *

+ * This method returns a historically correct offset value if an + * underlying TimeZone implementation subclass supports historical + * Daylight Saving Time schedule and GMT offset changes. + * + * @param date the date represented in milliseconds since January 1, 1970 00:00:00 GMT + * @return the amount of time in milliseconds to add to UTC to get local time. + * + * @see Calendar#ZONE_OFFSET + * @see Calendar#DST_OFFSET + * @since 1.4 + */ + public int getOffset(long date) { + if (inDaylightTime(new Date(date))) { + return getRawOffset() + getDSTSavings(); + } + return getRawOffset(); + } + + /** + * Gets the raw GMT offset and the amount of daylight saving of this + * time zone at the given time. + * @param date the milliseconds (since January 1, 1970, + * 00:00:00.000 GMT) at which the time zone offset and daylight + * saving amount are found + * @param offset an array of int where the raw GMT offset + * (offset[0]) and daylight saving amount (offset[1]) are stored, + * or null if those values are not needed. The method assumes that + * the length of the given array is two or larger. + * @return the total amount of the raw GMT offset and daylight + * saving at the specified date. + * + * @see Calendar#ZONE_OFFSET + * @see Calendar#DST_OFFSET + */ + int getOffsets(long date, int[] offsets) { + int rawoffset = getRawOffset(); + int dstoffset = 0; + if (inDaylightTime(new Date(date))) { + dstoffset = getDSTSavings(); + } + if (offsets != null) { + offsets[0] = rawoffset; + offsets[1] = dstoffset; + } + return rawoffset + dstoffset; + } + + /** + * Sets the base time zone offset to GMT. + * This is the offset to add to UTC to get local time. + *

+ * If an underlying TimeZone implementation subclass + * supports historical GMT offset changes, the specified GMT + * offset is set as the latest GMT offset and the difference from + * the known latest GMT offset value is used to adjust all + * historical GMT offset values. + * + * @param offsetMillis the given base time zone offset to GMT. + */ + abstract public void setRawOffset(int offsetMillis); + + /** + * Returns the amount of time in milliseconds to add to UTC to get + * standard time in this time zone. Because this value is not + * affected by daylight saving time, it is called raw + * offset. + *

+ * If an underlying TimeZone implementation subclass + * supports historical GMT offset changes, the method returns the + * raw offset value of the current date. In Honolulu, for example, + * its raw offset changed from GMT-10:30 to GMT-10:00 in 1947, and + * this method always returns -36000000 milliseconds (i.e., -10 + * hours). + * + * @return the amount of raw offset time in milliseconds to add to UTC. + * @see Calendar#ZONE_OFFSET + */ + public abstract int getRawOffset(); + + /** + * Gets the ID of this time zone. + * @return the ID of this time zone. + */ + public String getID() + { + return ID; + } + + /** + * Sets the time zone ID. This does not change any other data in + * the time zone object. + * @param ID the new time zone ID. + */ + public void setID(String ID) + { + if (ID == null) { + throw new NullPointerException(); + } + this.ID = ID; + } + + /** + * Returns a long standard time name of this {@code TimeZone} suitable for + * presentation to the user in the default locale. + * + *

This method is equivalent to: + *

+ * getDisplayName(false, {@link #LONG}, + * Locale.getDefault({@link Locale.Category#DISPLAY})) + *
+ * + * @return the human-readable name of this time zone in the default locale. + * @since 1.2 + * @see #getDisplayName(boolean, int, Locale) + * @see Locale#getDefault(Locale.Category) + * @see Locale.Category + */ + public final String getDisplayName() { + return getDisplayName(false, LONG, + Locale.getDefault(Locale.Category.DISPLAY)); + } + + /** + * Returns a long standard time name of this {@code TimeZone} suitable for + * presentation to the user in the specified {@code locale}. + * + *

This method is equivalent to: + *

+ * getDisplayName(false, {@link #LONG}, locale) + *
+ * + * @param locale the locale in which to supply the display name. + * @return the human-readable name of this time zone in the given locale. + * @exception NullPointerException if {@code locale} is {@code null}. + * @since 1.2 + * @see #getDisplayName(boolean, int, Locale) + */ + public final String getDisplayName(Locale locale) { + return getDisplayName(false, LONG, locale); + } + + /** + * Returns a name in the specified {@code style} of this {@code TimeZone} + * suitable for presentation to the user in the default locale. If the + * specified {@code daylight} is {@code true}, a Daylight Saving Time name + * is returned (even if this {@code TimeZone} doesn't observe Daylight Saving + * Time). Otherwise, a Standard Time name is returned. + * + *

This method is equivalent to: + *

+ * getDisplayName(daylight, style, + * Locale.getDefault({@link Locale.Category#DISPLAY})) + *
+ * + * @param daylight {@code true} specifying a Daylight Saving Time name, or + * {@code false} specifying a Standard Time name + * @param style either {@link #LONG} or {@link #SHORT} + * @return the human-readable name of this time zone in the default locale. + * @exception IllegalArgumentException if {@code style} is invalid. + * @since 1.2 + * @see #getDisplayName(boolean, int, Locale) + * @see Locale#getDefault(Locale.Category) + * @see Locale.Category + * @see java.text.DateFormatSymbols#getZoneStrings() + */ + public final String getDisplayName(boolean daylight, int style) { + return getDisplayName(daylight, style, + Locale.getDefault(Locale.Category.DISPLAY)); + } + + /** + * Returns a name in the specified {@code style} of this {@code TimeZone} + * suitable for presentation to the user in the specified {@code + * locale}. If the specified {@code daylight} is {@code true}, a Daylight + * Saving Time name is returned (even if this {@code TimeZone} doesn't + * observe Daylight Saving Time). Otherwise, a Standard Time name is + * returned. + * + *

When looking up a time zone name, the {@linkplain + * ResourceBundle.Control#getCandidateLocales(String,Locale) default + * Locale search path of ResourceBundle} derived + * from the specified {@code locale} is used. (No {@linkplain + * ResourceBundle.Control#getFallbackLocale(String,Locale) fallback + * Locale} search is performed.) If a time zone name in any + * {@code Locale} of the search path, including {@link Locale#ROOT}, is + * found, the name is returned. Otherwise, a string in the + * normalized custom ID format is returned. + * + * @param daylight {@code true} specifying a Daylight Saving Time name, or + * {@code false} specifying a Standard Time name + * @param style either {@link #LONG} or {@link #SHORT} + * @param locale the locale in which to supply the display name. + * @return the human-readable name of this time zone in the given locale. + * @exception IllegalArgumentException if {@code style} is invalid. + * @exception NullPointerException if {@code locale} is {@code null}. + * @since 1.2 + * @see java.text.DateFormatSymbols#getZoneStrings() + */ + public String getDisplayName(boolean daylight, int style, Locale locale) { + if (style != SHORT && style != LONG) { + throw new IllegalArgumentException("Illegal style: " + style); + } + + String id = getID(); + String[] names = getDisplayNames(id, locale); + if (names == null) { + if (id.startsWith("GMT")) { + char sign = id.charAt(3); + if (sign == '+' || sign == '-') { + return id; + } + } + int offset = getRawOffset(); + if (daylight) { + offset += getDSTSavings(); + } + return ZoneInfoFile.toCustomID(offset); + } + + int index = daylight ? 3 : 1; + if (style == SHORT) { + index++; + } + return names[index]; + } + + private static class DisplayNames { + // Cache for managing display names per timezone per locale + // The structure is: + // Map(key=id, value=SoftReference(Map(key=locale, value=displaynames))) + private static final Map>> CACHE = + new ConcurrentHashMap>>(); + } + + private static final String[] getDisplayNames(String id, Locale locale) { + Map>> displayNames = DisplayNames.CACHE; + + SoftReference> ref = displayNames.get(id); + if (ref != null) { + Map perLocale = ref.get(); + if (perLocale != null) { + String[] names = perLocale.get(locale); + if (names != null) { + return names; + } + names = TimeZoneNameUtility.retrieveDisplayNames(id, locale); + if (names != null) { + perLocale.put(locale, names); + } + return names; + } + } + + String[] names = TimeZoneNameUtility.retrieveDisplayNames(id, locale); + if (names != null) { + Map perLocale = new ConcurrentHashMap(); + perLocale.put(locale, names); + ref = new SoftReference>(perLocale); + displayNames.put(id, ref); + } + return names; + } + + /** + * Returns the amount of time to be added to local standard time + * to get local wall clock time. + * + *

The default implementation returns 3600000 milliseconds + * (i.e., one hour) if a call to {@link #useDaylightTime()} + * returns {@code true}. Otherwise, 0 (zero) is returned. + * + *

If an underlying {@code TimeZone} implementation subclass + * supports historical and future Daylight Saving Time schedule + * changes, this method returns the amount of saving time of the + * last known Daylight Saving Time rule that can be a future + * prediction. + * + *

If the amount of saving time at any given time stamp is + * required, construct a {@link Calendar} with this {@code + * TimeZone} and the time stamp, and call {@link Calendar#get(int) + * Calendar.get}{@code (}{@link Calendar#DST_OFFSET}{@code )}. + * + * @return the amount of saving time in milliseconds + * @since 1.4 + * @see #inDaylightTime(Date) + * @see #getOffset(long) + * @see #getOffset(int,int,int,int,int,int) + * @see Calendar#ZONE_OFFSET + */ + public int getDSTSavings() { + if (useDaylightTime()) { + return 3600000; + } + return 0; + } + + /** + * Queries if this {@code TimeZone} uses Daylight Saving Time. + * + *

If an underlying {@code TimeZone} implementation subclass + * supports historical and future Daylight Saving Time schedule + * changes, this method refers to the last known Daylight Saving Time + * rule that can be a future prediction and may not be the same as + * the current rule. Consider calling {@link #observesDaylightTime()} + * if the current rule should also be taken into account. + * + * @return {@code true} if this {@code TimeZone} uses Daylight Saving Time, + * {@code false}, otherwise. + * @see #inDaylightTime(Date) + * @see Calendar#DST_OFFSET + */ + public abstract boolean useDaylightTime(); + + /** + * Returns {@code true} if this {@code TimeZone} is currently in + * Daylight Saving Time, or if a transition from Standard Time to + * Daylight Saving Time occurs at any future time. + * + *

The default implementation returns {@code true} if + * {@code useDaylightTime()} or {@code inDaylightTime(new Date())} + * returns {@code true}. + * + * @return {@code true} if this {@code TimeZone} is currently in + * Daylight Saving Time, or if a transition from Standard Time to + * Daylight Saving Time occurs at any future time; {@code false} + * otherwise. + * @since 1.7 + * @see #useDaylightTime() + * @see #inDaylightTime(Date) + * @see Calendar#DST_OFFSET + */ + public boolean observesDaylightTime() { + return useDaylightTime() || inDaylightTime(new Date()); + } + + /** + * Queries if the given {@code date} is in Daylight Saving Time in + * this time zone. + * + * @param date the given Date. + * @return {@code true} if the given date is in Daylight Saving Time, + * {@code false}, otherwise. + */ + abstract public boolean inDaylightTime(Date date); + + /** + * Gets the TimeZone for the given ID. + * + * @param ID the ID for a TimeZone, either an abbreviation + * such as "PST", a full name such as "America/Los_Angeles", or a custom + * ID such as "GMT-8:00". Note that the support of abbreviations is + * for JDK 1.1.x compatibility only and full names should be used. + * + * @return the specified TimeZone, or the GMT zone if the given ID + * cannot be understood. + */ + public static synchronized TimeZone getTimeZone(String ID) { + return getTimeZone(ID, true); + } + + private static TimeZone getTimeZone(String ID, boolean fallback) { + TimeZone tz = ZoneInfo.getTimeZone(ID); + if (tz == null) { + tz = parseCustomTimeZone(ID); + if (tz == null && fallback) { + tz = new ZoneInfo(GMT_ID, 0); + } + } + return tz; + } + + /** + * Gets the available IDs according to the given time zone offset in milliseconds. + * + * @param rawOffset the given time zone GMT offset in milliseconds. + * @return an array of IDs, where the time zone for that ID has + * the specified GMT offset. For example, "America/Phoenix" and "America/Denver" + * both have GMT-07:00, but differ in daylight saving behavior. + * @see #getRawOffset() + */ + public static synchronized String[] getAvailableIDs(int rawOffset) { + return ZoneInfo.getAvailableIDs(rawOffset); + } + + /** + * Gets all the available IDs supported. + * @return an array of IDs. + */ + public static synchronized String[] getAvailableIDs() { + return ZoneInfo.getAvailableIDs(); + } + + /** + * Gets the platform defined TimeZone ID. + **/ + private static native String getSystemTimeZoneID(String javaHome, + String country); + + /** + * Gets the custom time zone ID based on the GMT offset of the + * platform. (e.g., "GMT+08:00") + */ + private static native String getSystemGMTOffsetID(); + + /** + * Gets the default TimeZone for this host. + * The source of the default TimeZone + * may vary with implementation. + * @return a default TimeZone. + * @see #setDefault + */ + public static TimeZone getDefault() { + return (TimeZone) getDefaultRef().clone(); + } + + /** + * Returns the reference to the default TimeZone object. This + * method doesn't create a clone. + */ + static TimeZone getDefaultRef() { + TimeZone defaultZone = defaultZoneTL.get(); + if (defaultZone == null) { + defaultZone = defaultTimeZone; + if (defaultZone == null) { + // Need to initialize the default time zone. + defaultZone = setDefaultZone(); + assert defaultZone != null; + } + } + // Don't clone here. + return defaultZone; + } + + private static synchronized TimeZone setDefaultZone() { + TimeZone tz = null; + // get the time zone ID from the system properties + String zoneID = AccessController.doPrivileged( + new GetPropertyAction("user.timezone")); + + // if the time zone ID is not set (yet), perform the + // platform to Java time zone ID mapping. + if (zoneID == null || zoneID.equals("")) { + String country = AccessController.doPrivileged( + new GetPropertyAction("user.country")); + String javaHome = AccessController.doPrivileged( + new GetPropertyAction("java.home")); + try { + zoneID = getSystemTimeZoneID(javaHome, country); + if (zoneID == null) { + zoneID = GMT_ID; + } + } catch (NullPointerException e) { + zoneID = GMT_ID; + } + } + + // Get the time zone for zoneID. But not fall back to + // "GMT" here. + tz = getTimeZone(zoneID, false); + + if (tz == null) { + // If the given zone ID is unknown in Java, try to + // get the GMT-offset-based time zone ID, + // a.k.a. custom time zone ID (e.g., "GMT-08:00"). + String gmtOffsetID = getSystemGMTOffsetID(); + if (gmtOffsetID != null) { + zoneID = gmtOffsetID; + } + tz = getTimeZone(zoneID, true); + } + assert tz != null; + + final String id = zoneID; + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + System.setProperty("user.timezone", id); + return null; + } + }); + + defaultTimeZone = tz; + return tz; + } + + private static boolean hasPermission() { + boolean hasPermission = true; + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + try { + sm.checkPermission(new PropertyPermission + ("user.timezone", "write")); + } catch (SecurityException e) { + hasPermission = false; + } + } + return hasPermission; + } + + /** + * Sets the TimeZone that is + * returned by the getDefault method. If zone + * is null, reset the default to the value it had originally when the + * VM first started. + * @param zone the new default time zone + * @see #getDefault + */ + public static void setDefault(TimeZone zone) + { + if (hasPermission()) { + synchronized (TimeZone.class) { + defaultTimeZone = zone; + defaultZoneTL.set(null); + } + } else { + defaultZoneTL.set(zone); + } + } + + /** + * Returns true if this zone has the same rule and offset as another zone. + * That is, if this zone differs only in ID, if at all. Returns false + * if the other zone is null. + * @param other the TimeZone object to be compared with + * @return true if the other zone is not null and is the same as this one, + * with the possible exception of the ID + * @since 1.2 + */ + public boolean hasSameRules(TimeZone other) { + return other != null && getRawOffset() == other.getRawOffset() && + useDaylightTime() == other.useDaylightTime(); + } + + /** + * Creates a copy of this TimeZone. + * + * @return a clone of this TimeZone + */ + public Object clone() + { + try { + TimeZone other = (TimeZone) super.clone(); + other.ID = ID; + return other; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * The null constant as a TimeZone. + */ + static final TimeZone NO_TIMEZONE = null; + + // =======================privates=============================== + + /** + * The string identifier of this TimeZone. This is a + * programmatic identifier used internally to look up TimeZone + * objects from the system table and also to map them to their localized + * display names. ID values are unique in the system + * table but may not be for dynamically created zones. + * @serial + */ + private String ID; + private static volatile TimeZone defaultTimeZone; + private static final InheritableThreadLocal defaultZoneTL + = new InheritableThreadLocal(); + + static final String GMT_ID = "GMT"; + private static final int GMT_ID_LENGTH = 3; + + /** + * Parses a custom time zone identifier and returns a corresponding zone. + * This method doesn't support the RFC 822 time zone format. (e.g., +hhmm) + * + * @param id a string of the custom ID form. + * @return a newly created TimeZone with the given offset and + * no daylight saving time, or null if the id cannot be parsed. + */ + private static final TimeZone parseCustomTimeZone(String id) { + int length; + + // Error if the length of id isn't long enough or id doesn't + // start with "GMT". + if ((length = id.length()) < (GMT_ID_LENGTH + 2) || + id.indexOf(GMT_ID) != 0) { + return null; + } + + ZoneInfo zi; + + // First, we try to find it in the cache with the given + // id. Even the id is not normalized, the returned ZoneInfo + // should have its normalized id. + zi = ZoneInfoFile.getZoneInfo(id); + if (zi != null) { + return zi; + } + + int index = GMT_ID_LENGTH; + boolean negative = false; + char c = id.charAt(index++); + if (c == '-') { + negative = true; + } else if (c != '+') { + return null; + } + + int hours = 0; + int num = 0; + int countDelim = 0; + int len = 0; + while (index < length) { + c = id.charAt(index++); + if (c == ':') { + if (countDelim > 0) { + return null; + } + if (len > 2) { + return null; + } + hours = num; + countDelim++; + num = 0; + len = 0; + continue; + } + if (c < '0' || c > '9') { + return null; + } + num = num * 10 + (c - '0'); + len++; + } + if (index != length) { + return null; + } + if (countDelim == 0) { + if (len <= 2) { + hours = num; + num = 0; + } else { + hours = num / 100; + num %= 100; + } + } else { + if (len != 2) { + return null; + } + } + if (hours > 23 || num > 59) { + return null; + } + int gmtOffset = (hours * 60 + num) * 60 * 1000; + + if (gmtOffset == 0) { + zi = ZoneInfoFile.getZoneInfo(GMT_ID); + if (negative) { + zi.setID("GMT-00:00"); + } else { + zi.setID("GMT+00:00"); + } + } else { + zi = ZoneInfoFile.getCustomTimeZone(id, negative ? -gmtOffset : gmtOffset); + } + return zi; + } +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/ConcurrentHashMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/ConcurrentHashMap.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,1522 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent; +import java.util.concurrent.locks.*; +import java.util.*; +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * A hash table supporting full concurrency of retrievals and + * adjustable 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 + * Hashtable. However, even though all operations are + * thread-safe, retrieval operations do not entail locking, + * and there is not any support for locking the entire table + * in a way that prevents all access. This class is fully + * interoperable with Hashtable in programs that rely on its + * thread safety but not on its synchronization details. + * + *

Retrieval operations (including get) generally do not + * block, so may overlap with update operations (including + * put and remove). Retrievals reflect the results + * of the most recently completed update operations holding + * upon their onset. For aggregate operations such as putAll + * and 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 not throw {@link ConcurrentModificationException}. + * However, iterators are designed to be used by only one thread at a time. + * + *

The allowed concurrency among update operations is guided by + * the optional concurrencyLevel constructor argument + * (default 16), 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. + * + *

This class and its views and iterators implement all of the + * optional methods of the {@link Map} and {@link Iterator} + * interfaces. + * + *

Like {@link Hashtable} but unlike {@link HashMap}, this class + * does not allow null to be used as a key or value. + * + *

This class is a member of the + * + * Java Collections Framework. + * + * @since 1.5 + * @author Doug Lea + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class ConcurrentHashMap extends AbstractMap + implements ConcurrentMap, 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. + */ + + /* ---------------- Constants -------------- */ + + /** + * The default initial capacity for this table, + * used when not otherwise specified in a constructor. + */ + static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The default load factor for this table, used when not + * otherwise specified in a constructor. + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The default concurrency level for this table, used when not + * otherwise specified in a constructor. + */ + static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * 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. + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * 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. + */ + static final int MIN_SEGMENT_TABLE_CAPACITY = 2; + + /** + * The maximum number of segments to allow; used to bound + * constructor arguments. Must be power of two less than 1 << 24. + */ + static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + + /** + * 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. + */ + static final int RETRIES_BEFORE_LOCK = 2; + + /* ---------------- Fields -------------- */ + + /** + * Mask value for indexing into segments. The upper bits of a + * key's hash code are used to choose the segment. + */ + final int segmentMask; + + /** + * Shift value for indexing within segments. + */ + final int segmentShift; + + /** + * The segments, each of which is a specialized hash table. + */ + final Segment[] segments; + + transient Set keySet; + transient Set> entrySet; + transient Collection values; + + /** + * ConcurrentHashMap list entry. Note that this is never exported + * out as a user-visible Map.Entry. + */ + static final class HashEntry { + final int hash; + final K key; + volatile V value; + volatile HashEntry next; + + HashEntry(int hash, K key, V value, HashEntry next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + + /** + * Sets next field with volatile write semantics. (See above + * about use of putOrderedObject.) + */ + final void setNext(HashEntry n) { + UNSAFE.putOrderedObject(this, nextOffset, n); + } + + // 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); + } + } + } + + /** + * 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. + */ + @SuppressWarnings("unchecked") + static final HashEntry entryAt(HashEntry[] tab, int i) { + return (tab == null) ? null : + (HashEntry) UNSAFE.getObjectVolatile + (tab, ((long)i << TSHIFT) + TBASE); + } + + /** + * Sets the ith element of given table, with volatile write + * semantics. (See above about use of putOrderedObject.) + */ + static final void setEntryAt(HashEntry[] tab, int i, + HashEntry e) { + UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e); + } + + /** + * 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. + */ + 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); + } + + /** + * Segments are specialized versions of hash tables. This + * subclasses from ReentrantLock opportunistically, just to + * simplify some locking and avoid separate construction. + */ + static final class Segment 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. + */ + + private static final long serialVersionUID = 2249069246763182397L; + + /** + * 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; + + /** + * The per-segment table. Elements are accessed via + * entryAt/setEntryAt providing volatile semantics. + */ + transient volatile HashEntry[] table; + + /** + * The number of elements. Accessed only either within locks + * or among other volatile reads that maintain visibility. + */ + transient int count; + + /** + * 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; + + /** + * The table is rehashed when its size exceeds this threshold. + * (The value of this field is always (int)(capacity * + * loadFactor).) + */ + transient int threshold; + + /** + * 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; + + Segment(float lf, int threshold, HashEntry[] tab) { + this.loadFactor = lf; + this.threshold = threshold; + this.table = tab; + } + + final V put(K key, int hash, V value, boolean onlyIfAbsent) { + HashEntry node = tryLock() ? null : + scanAndLockForPut(key, hash, value); + V oldValue; + try { + HashEntry[] tab = table; + int index = (tab.length - 1) & hash; + HashEntry first = entryAt(tab, index); + for (HashEntry 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(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; + } + + /** + * Doubles size of table and repacks entries, also adding the + * given node to new table + */ + @SuppressWarnings("unchecked") + private void rehash(HashEntry 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[] oldTable = table; + int oldCapacity = oldTable.length; + int newCapacity = oldCapacity << 1; + threshold = (int)(newCapacity * loadFactor); + HashEntry[] newTable = + (HashEntry[]) new HashEntry[newCapacity]; + int sizeMask = newCapacity - 1; + for (int i = 0; i < oldCapacity ; i++) { + HashEntry e = oldTable[i]; + if (e != null) { + HashEntry 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 lastRun = e; + int lastIdx = idx; + for (HashEntry 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 p = e; p != lastRun; p = p.next) { + V v = p.value; + int h = p.hash; + int k = h & sizeMask; + HashEntry n = newTable[k]; + newTable[k] = new HashEntry(h, p.key, v, n); + } + } + } + } + int nodeIndex = node.hash & sizeMask; // add the new node + node.setNext(newTable[nodeIndex]); + newTable[nodeIndex] = node; + table = newTable; + } + + /** + * 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 scanAndLockForPut(K key, int hash, V value) { + HashEntry first = entryForHash(this, hash); + HashEntry e = first; + HashEntry node = null; + int retries = -1; // negative while locating node + while (!tryLock()) { + HashEntry f; // to recheck first below + if (retries < 0) { + if (e == null) { + if (node == null) // speculatively create node + node = new HashEntry(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; + } + + /** + * 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 first = entryForHash(this, hash); + HashEntry e = first; + int retries = -1; + while (!tryLock()) { + HashEntry 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; + } + } + } + + /** + * 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[] tab = table; + int index = (tab.length - 1) & hash; + HashEntry e = entryAt(tab, index); + HashEntry pred = null; + while (e != null) { + K k; + HashEntry 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; + } + + final boolean replace(K key, int hash, V oldValue, V newValue) { + if (!tryLock()) + scanAndLock(key, hash); + boolean replaced = false; + try { + HashEntry 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; + } + + final V replace(K key, int hash, V value) { + if (!tryLock()) + scanAndLock(key, hash); + V oldValue = null; + try { + HashEntry 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; + } + + final void clear() { + lock(); + try { + HashEntry[] tab = table; + for (int i = 0; i < tab.length ; i++) + setEntryAt(tab, i, null); + ++modCount; + count = 0; + } finally { + unlock(); + } + } + } + + // Accessing segments + + /** + * 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. + */ + @SuppressWarnings("unchecked") + static final Segment segmentAt(Segment[] ss, int j) { + long u = (j << SSHIFT) + SBASE; + return ss == null ? null : + (Segment) UNSAFE.getObjectVolatile(ss, u); + } + + /** + * Returns the segment for the given index, creating it and + * recording in segment table (via CAS) if not already present. + * + * @param k the index + * @return the segment + */ + @SuppressWarnings("unchecked") + private Segment ensureSegment(int k) { + final Segment[] ss = this.segments; + long u = (k << SSHIFT) + SBASE; // raw offset + Segment seg; + if ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) == null) { + Segment proto = ss[0]; // use segment 0 as prototype + int cap = proto.table.length; + float lf = proto.loadFactor; + int threshold = (int)(cap * lf); + HashEntry[] tab = (HashEntry[])new HashEntry[cap]; + if ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) + == null) { // recheck + Segment s = new Segment(lf, threshold, tab); + while ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) + == null) { + if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) + break; + } + } + } + return seg; + } + + // Hash-based segment and entry accesses + + /** + * Get the segment for the given hash + */ + @SuppressWarnings("unchecked") + private Segment segmentForHash(int h) { + long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; + return (Segment) UNSAFE.getObjectVolatile(segments, u); + } + + /** + * Gets the table entry for the given segment and hash + */ + @SuppressWarnings("unchecked") + static final HashEntry entryForHash(Segment seg, int h) { + HashEntry[] tab; + return (seg == null || (tab = seg.table) == null) ? null : + (HashEntry) UNSAFE.getObjectVolatile + (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); + } + + /* ---------------- Public operations -------------- */ + + /** + * Creates a new, empty map with the specified initial + * capacity, load factor and concurrency level. + * + * @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 s0 = + new Segment(loadFactor, (int)(cap * loadFactor), + (HashEntry[])new HashEntry[cap]); + Segment[] ss = (Segment[])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. + * @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); + } + + /** + * Creates a new, empty map with the specified initial capacity, + * and with default load factor (0.75) and concurrencyLevel (16). + * + * @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. + */ + public ConcurrentHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * 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); + } + + /** + * 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 + */ + public ConcurrentHashMap(Map m) { + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, + DEFAULT_INITIAL_CAPACITY), + DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + putAll(m); + } + + /** + * Returns true if this map contains no key-value mappings. + * + * @return true if this map contains no key-value mappings + */ + 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[] segments = this.segments; + for (int j = 0; j < segments.length; ++j) { + Segment 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 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 Integer.MAX_VALUE elements, returns + * Integer.MAX_VALUE. + * + * @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[] segments = this.segments; + int size; + boolean overflow; // true if size overflows 32 bits + long sum; // sum of modCounts + long last = 0L; // previous sum + int retries = -1; // first iteration isn't retry + try { + for (;;) { + if (retries++ == RETRIES_BEFORE_LOCK) { + for (int j = 0; j < segments.length; ++j) + ensureSegment(j).lock(); // force creation + } + sum = 0L; + size = 0; + overflow = false; + for (int j = 0; j < segments.length; ++j) { + Segment seg = segmentAt(segments, j); + if (seg != null) { + sum += seg.modCount; + int c = seg.count; + if (c < 0 || (size += c) < 0) + overflow = true; + } + } + if (sum == last) + break; + last = sum; + } + } finally { + if (retries > RETRIES_BEFORE_LOCK) { + for (int j = 0; j < segments.length; ++j) + segmentAt(segments, j).unlock(); + } + } + return overflow ? Integer.MAX_VALUE : size; + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code key.equals(k)}, + * then this method returns {@code v}; otherwise it returns + * {@code null}. (There can be at most one such mapping.) + * + * @throws NullPointerException if the specified key is null + */ + public V get(Object key) { + Segment s; // manually integrate access methods to reduce overhead + HashEntry[] tab; + int h = hash(key.hashCode()); + long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; + if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null && + (tab = s.table) != null) { + for (HashEntry e = (HashEntry) 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; + } + } + return null; + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return true if and only if the specified object + * is a key in this table, as determined by the + * equals method; false otherwise. + * @throws NullPointerException if the specified key is null + */ + @SuppressWarnings("unchecked") + public boolean containsKey(Object key) { + Segment s; // same as get() except no need for volatile value read + HashEntry[] tab; + int h = hash(key.hashCode()); + long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; + if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null && + (tab = s.table) != null) { + for (HashEntry e = (HashEntry) 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; + } + + /** + * Returns true 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 containsKey. + * + * @param value value whose presence in this map is to be tested + * @return 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[] segments = this.segments; + boolean found = false; + long last = 0; + int retries = -1; + try { + outer: for (;;) { + if (retries++ == RETRIES_BEFORE_LOCK) { + for (int j = 0; j < segments.length; ++j) + ensureSegment(j).lock(); // force creation + } + long hashSum = 0L; + int sum = 0; + for (int j = 0; j < segments.length; ++j) { + HashEntry[] tab; + Segment seg = segmentAt(segments, j); + if (seg != null && (tab = seg.table) != null) { + for (int i = 0 ; i < tab.length; i++) { + HashEntry e; + for (e = entryAt(tab, i); e != null; e = e.next) { + V v = e.value; + if (v != null && value.equals(v)) { + found = true; + break outer; + } + } + } + sum += seg.modCount; + } + } + if (retries > 0 && sum == last) + break; + last = sum; + } + } finally { + if (retries > RETRIES_BEFORE_LOCK) { + for (int j = 0; j < segments.length; ++j) + segmentAt(segments, j).unlock(); + } + } + return found; + } + + /** + * 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 true if and only if some key maps to the + * value argument in this table as + * determined by the equals method; + * false otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Maps the specified key to the specified value in this table. + * Neither the key nor the value can be null. + * + *

The value can be retrieved by calling the 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 key, or + * null if there was no mapping for key + * @throws NullPointerException if the specified key or value is null + */ + @SuppressWarnings("unchecked") + public V put(K key, V value) { + Segment s; + if (value == null) + throw new NullPointerException(); + int hash = hash(key.hashCode()); + int j = (hash >>> segmentShift) & segmentMask; + if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck + (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment + s = ensureSegment(j); + return s.put(key, hash, value, false); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or null 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 s; + if (value == null) + throw new NullPointerException(); + int hash = hash(key.hashCode()); + int j = (hash >>> segmentShift) & segmentMask; + if ((s = (Segment)UNSAFE.getObject + (segments, (j << SSHIFT) + SBASE)) == null) + s = ensureSegment(j); + return s.put(key, hash, value, true); + } + + /** + * Copies all of the mappings from the specified map to this one. + * These mappings replace any mappings that this map had for any of the + * keys currently in the specified map. + * + * @param m mappings to be stored in this map + */ + public void putAll(Map m) { + for (Map.Entry e : m.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * Removes the key (and its corresponding value) from this map. + * 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 key, or + * null if there was no mapping for key + * @throws NullPointerException if the specified key is null + */ + public V remove(Object key) { + int hash = hash(key.hashCode()); + Segment 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 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 s = segmentForHash(hash); + return s != null && s.replace(key, hash, oldValue, newValue); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or 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) { + int hash = hash(key.hashCode()); + if (value == null) + throw new NullPointerException(); + Segment s = segmentForHash(hash); + return s == null ? null : s.replace(key, hash, value); + } + + /** + * Removes all of the mappings from this map. + */ + public void clear() { + final Segment[] segments = this.segments; + for (int j = 0; j < segments.length; ++j) { + Segment s = segmentAt(segments, j); + if (s != null) + s.clear(); + } + } + + /** + * 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 + * removal, which removes the corresponding mapping from this map, + * via the Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or + * addAll operations. + * + *

The view's 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. + */ + public Set keySet() { + Set ks = keySet; + return (ks != null) ? ks : (keySet = new KeySet()); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * 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 Iterator.remove, + * Collection.remove, removeAll, + * retainAll, and clear operations. It does not + * support the add or addAll operations. + * + *

The view's 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. + */ + public Collection values() { + Collection vs = values; + return (vs != null) ? vs : (values = new Values()); + } + + /** + * Returns a {@link Set} view of the mappings 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 + * removal, which removes the corresponding mapping from the map, + * via the Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or + * addAll operations. + * + *

The view's 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. + */ + public Set> entrySet() { + Set> es = entrySet; + return (es != null) ? es : (entrySet = new EntrySet()); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration keys() { + return new KeyIterator(); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration elements() { + return new ValueIterator(); + } + + /* ---------------- Iterator Support -------------- */ + + abstract class HashIterator { + int nextSegmentIndex; + int nextTableIndex; + HashEntry[] currentTable; + HashEntry nextEntry; + HashEntry lastReturned; + + HashIterator() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + advance(); + } + + /** + * Set nextEntry to first node of next non-empty table + * (in backwards order, to simplify checks). + */ + final void advance() { + for (;;) { + if (nextTableIndex >= 0) { + if ((nextEntry = entryAt(currentTable, + nextTableIndex--)) != null) + break; + } + else if (nextSegmentIndex >= 0) { + Segment seg = segmentAt(segments, nextSegmentIndex--); + if (seg != null && (currentTable = seg.table) != null) + nextTableIndex = currentTable.length - 1; + } + else + break; + } + } + + final HashEntry nextEntry() { + HashEntry e = nextEntry; + if (e == null) + throw new NoSuchElementException(); + lastReturned = e; // cannot assign until after null check + if ((nextEntry = e.next) == null) + advance(); + return e; + } + + public final boolean hasNext() { return nextEntry != null; } + public final boolean hasMoreElements() { return nextEntry != null; } + + public final void remove() { + if (lastReturned == null) + throw new IllegalStateException(); + ConcurrentHashMap.this.remove(lastReturned.key); + lastReturned = null; + } + } + + final class KeyIterator + extends HashIterator + implements Iterator, Enumeration + { + public final K next() { return super.nextEntry().key; } + public final K nextElement() { return super.nextEntry().key; } + } + + final class ValueIterator + extends HashIterator + implements Iterator, Enumeration + { + public final V next() { return super.nextEntry().value; } + public final V nextElement() { return super.nextEntry().value; } + } + + /** + * Custom Entry class used by EntryIterator.next(), that relays + * setValue changes to the underlying map. + */ + final class WriteThroughEntry + extends AbstractMap.SimpleEntry + { + WriteThroughEntry(K k, V v) { + super(k,v); + } + + /** + * Set our entry's value and write 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. + */ + public V setValue(V value) { + if (value == null) throw new NullPointerException(); + V v = super.setValue(value); + ConcurrentHashMap.this.put(getKey(), value); + return v; + } + } + + final class EntryIterator + extends HashIterator + implements Iterator> + { + public Map.Entry next() { + HashEntry e = super.nextEntry(); + return new WriteThroughEntry(e.key, e.value); + } + } + + final class KeySet extends AbstractSet { + public Iterator iterator() { + return new KeyIterator(); + } + public int size() { + return ConcurrentHashMap.this.size(); + } + public boolean isEmpty() { + return ConcurrentHashMap.this.isEmpty(); + } + public boolean contains(Object o) { + return ConcurrentHashMap.this.containsKey(o); + } + public boolean remove(Object o) { + return ConcurrentHashMap.this.remove(o) != null; + } + public void clear() { + ConcurrentHashMap.this.clear(); + } + } + + final class Values extends AbstractCollection { + public Iterator iterator() { + return new ValueIterator(); + } + public int size() { + return ConcurrentHashMap.this.size(); + } + public boolean isEmpty() { + return ConcurrentHashMap.this.isEmpty(); + } + public boolean contains(Object o) { + return ConcurrentHashMap.this.containsValue(o); + } + public void clear() { + ConcurrentHashMap.this.clear(); + } + } + + final class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(); + } + 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()); + } + 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()); + } + public int size() { + return ConcurrentHashMap.this.size(); + } + public boolean isEmpty() { + return ConcurrentHashMap.this.isEmpty(); + } + public void clear() { + ConcurrentHashMap.this.clear(); + } + } + + /* ---------------- Serialization Support -------------- */ + + /** + * Save the state of the ConcurrentHashMap instance to a + * stream (i.e., serialize 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 IOException { + // force all segments for serialization compatibility + for (int k = 0; k < segments.length; ++k) + ensureSegment(k); + s.defaultWriteObject(); + + final Segment[] segments = this.segments; + for (int k = 0; k < segments.length; ++k) { + Segment seg = segmentAt(segments, k); + seg.lock(); + try { + HashEntry[] tab = seg.table; + for (int i = 0; i < tab.length; ++i) { + HashEntry 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); + } + + /** + * Reconstitute the ConcurrentHashMap instance from a + * stream (i.e., deserialize it). + * @param s the stream + */ + @SuppressWarnings("unchecked") + private void readObject(java.io.ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + + // Re-initialize segments to be minimally sized, and let grow. + int cap = MIN_SEGMENT_TABLE_CAPACITY; + final Segment[] segments = this.segments; + for (int k = 0; k < segments.length; ++k) { + Segment seg = segments[k]; + if (seg != null) { + seg.threshold = (int)(cap * seg.loadFactor); + seg.table = (HashEntry[]) new HashEntry[cap]; + } + } + + // Read the keys and values, and put the mappings in the table + for (;;) { + K key = (K) s.readObject(); + V value = (V) s.readObject(); + if (key == null) + break; + put(key, value); + } + } + + // 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; + + 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); + } 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 -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/ConcurrentMap.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/ConcurrentMap.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,165 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent; +import java.util.Map; + +/** + * A {@link java.util.Map} providing additional atomic + * putIfAbsent, remove, and replace methods. + * + *

Memory consistency effects: As with other concurrent + * collections, actions in a thread prior to placing an object into a + * {@code ConcurrentMap} as a key or value + * happen-before + * actions subsequent to the access or removal of that object from + * the {@code ConcurrentMap} in another thread. + * + *

This interface is a member of the + * + * Java Collections Framework. + * + * @since 1.5 + * @author Doug Lea + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public interface ConcurrentMap extends Map { + /** + * If the specified key is not already associated + * with a value, associate it with the given value. + * This is equivalent to + *

+     *   if (!map.containsKey(key))
+     *       return map.put(key, value);
+     *   else
+     *       return map.get(key);
+ * except that the action is performed atomically. + * + * @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 the specified key, or + * null if there was no mapping for the key. + * (A null return can also indicate that the map + * previously associated null with the key, + * if the implementation supports null values.) + * @throws UnsupportedOperationException if the put operation + * is not supported by this map + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * + */ + V putIfAbsent(K key, V value); + + /** + * Removes the entry for a key only if currently mapped to a given value. + * This is equivalent to + *
+     *   if (map.containsKey(key) && map.get(key).equals(value)) {
+     *       map.remove(key);
+     *       return true;
+     *   } else return false;
+ * except that the action is performed atomically. + * + * @param key key with which the specified value is associated + * @param value value expected to be associated with the specified key + * @return true if the value was removed + * @throws UnsupportedOperationException if the remove operation + * is not supported by this map + * @throws ClassCastException if the key or value is of an inappropriate + * type for this map + * (optional) + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * (optional) + */ + boolean remove(Object key, Object value); + + /** + * Replaces the entry for a key only if currently mapped to a given value. + * This is equivalent to + *
+     *   if (map.containsKey(key) && map.get(key).equals(oldValue)) {
+     *       map.put(key, newValue);
+     *       return true;
+     *   } else return false;
+ * except that the action is performed atomically. + * + * @param key key with which the specified value is associated + * @param oldValue value expected to be associated with the specified key + * @param newValue value to be associated with the specified key + * @return true if the value was replaced + * @throws UnsupportedOperationException if the put operation + * is not supported by this map + * @throws ClassCastException if the class of a specified key or value + * prevents it from being stored in this map + * @throws NullPointerException if a specified key or value is null, + * and this map does not permit null keys or values + * @throws IllegalArgumentException if some property of a specified key + * or value prevents it from being stored in this map + */ + boolean replace(K key, V oldValue, V newValue); + + /** + * Replaces the entry for a key only if currently mapped to some value. + * This is equivalent to + *
+     *   if (map.containsKey(key)) {
+     *       return map.put(key, value);
+     *   } else return null;
+ * except that the action is performed atomically. + * + * @param key key with which the specified value is associated + * @param value value to be associated with the specified key + * @return the previous value associated with the specified key, or + * null if there was no mapping for the key. + * (A null return can also indicate that the map + * previously associated null with the key, + * if the implementation supports null values.) + * @throws UnsupportedOperationException if the put operation + * is not supported by this map + * @throws ClassCastException if the class of the specified key or value + * prevents it from being stored in this map + * @throws NullPointerException if the specified key or value is null, + * and this map does not permit null keys or values + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + */ + V replace(K key, V value); +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicBoolean.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicBoolean.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,164 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; +import sun.misc.Unsafe; + +/** + * A {@code boolean} value that may be updated atomically. See the + * {@link java.util.concurrent.atomic} package specification for + * description of the properties of atomic variables. An + * {@code AtomicBoolean} is used in applications such as atomically + * updated flags, and cannot be used as a replacement for a + * {@link java.lang.Boolean}. + * + * @since 1.5 + * @author Doug Lea + */ +public class AtomicBoolean implements java.io.Serializable { + private static final long serialVersionUID = 4654671469794556979L; + // setup to use Unsafe.compareAndSwapInt for updates + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicBoolean.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile int value; + + /** + * Creates a new {@code AtomicBoolean} with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicBoolean(boolean initialValue) { + value = initialValue ? 1 : 0; + } + + /** + * Creates a new {@code AtomicBoolean} with initial value {@code false}. + */ + public AtomicBoolean() { + } + + /** + * Returns the current value. + * + * @return the current value + */ + public final boolean get() { + return value != 0; + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(boolean expect, boolean update) { + int e = expect ? 1 : 0; + int u = update ? 1 : 0; + return unsafe.compareAndSwapInt(this, valueOffset, e, u); + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public boolean weakCompareAndSet(boolean expect, boolean update) { + int e = expect ? 1 : 0; + int u = update ? 1 : 0; + return unsafe.compareAndSwapInt(this, valueOffset, e, u); + } + + /** + * Unconditionally sets to the given value. + * + * @param newValue the new value + */ + public final void set(boolean newValue) { + value = newValue ? 1 : 0; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(boolean newValue) { + int v = newValue ? 1 : 0; + unsafe.putOrderedInt(this, valueOffset, v); + } + + /** + * Atomically sets to the given value and returns the previous value. + * + * @param newValue the new value + * @return the previous value + */ + public final boolean getAndSet(boolean newValue) { + for (;;) { + boolean current = get(); + if (compareAndSet(current, newValue)) + return current; + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value. + */ + public String toString() { + return Boolean.toString(get()); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicInteger.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicInteger.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,265 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; +import sun.misc.Unsafe; + +/** + * An {@code int} value that may be updated atomically. See the + * {@link java.util.concurrent.atomic} package specification for + * description of the properties of atomic variables. An + * {@code AtomicInteger} is used in applications such as atomically + * incremented counters, and cannot be used as a replacement for an + * {@link java.lang.Integer}. However, this class does extend + * {@code Number} to allow uniform access by tools and utilities that + * deal with numerically-based classes. + * + * @since 1.5 + * @author Doug Lea +*/ +public class AtomicInteger extends Number implements java.io.Serializable { + private static final long serialVersionUID = 6214790243416807050L; + + // setup to use Unsafe.compareAndSwapInt for updates + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile int value; + + /** + * Creates a new AtomicInteger with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicInteger(int initialValue) { + value = initialValue; + } + + /** + * Creates a new AtomicInteger with initial value {@code 0}. + */ + public AtomicInteger() { + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final int get() { + return value; + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public final void set(int newValue) { + value = newValue; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int newValue) { + unsafe.putOrderedInt(this, valueOffset, newValue); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + public final int getAndSet(int newValue) { + for (;;) { + int current = get(); + if (compareAndSet(current, newValue)) + return current; + } + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int expect, int update) { + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(int expect, int update) { + return unsafe.compareAndSwapInt(this, valueOffset, expect, update); + } + + /** + * Atomically increments by one the current value. + * + * @return the previous value + */ + public final int getAndIncrement() { + for (;;) { + int current = get(); + int next = current + 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically decrements by one the current value. + * + * @return the previous value + */ + public final int getAndDecrement() { + for (;;) { + int current = get(); + int next = current - 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the previous value + */ + public final int getAndAdd(int delta) { + for (;;) { + int current = get(); + int next = current + delta; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically increments by one the current value. + * + * @return the updated value + */ + public final int incrementAndGet() { + for (;;) { + int current = get(); + int next = current + 1; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Atomically decrements by one the current value. + * + * @return the updated value + */ + public final int decrementAndGet() { + for (;;) { + int current = get(); + int next = current - 1; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the updated value + */ + public final int addAndGet(int delta) { + for (;;) { + int current = get(); + int next = current + delta; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value. + */ + public String toString() { + return Integer.toString(get()); + } + + + public int intValue() { + return get(); + } + + public long longValue() { + return (long)get(); + } + + public float floatValue() { + return (float)get(); + } + + public double doubleValue() { + return (double)get(); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicIntegerArray.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicIntegerArray.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,284 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; +import sun.misc.Unsafe; +import java.util.*; + +/** + * An {@code int} array in which elements may be updated atomically. + * See the {@link java.util.concurrent.atomic} package + * specification for description of the properties of atomic + * variables. + * @since 1.5 + * @author Doug Lea + */ +public class AtomicIntegerArray implements java.io.Serializable { + private static final long serialVersionUID = 2862133569453604235L; + + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final int base = unsafe.arrayBaseOffset(int[].class); + private static final int shift; + private final int[] array; + + static { + int scale = unsafe.arrayIndexScale(int[].class); + if ((scale & (scale - 1)) != 0) + throw new Error("data type scale not a power of two"); + shift = 31 - Integer.numberOfLeadingZeros(scale); + } + + private long checkedByteOffset(int i) { + if (i < 0 || i >= array.length) + throw new IndexOutOfBoundsException("index " + i); + + return byteOffset(i); + } + + private static long byteOffset(int i) { + return ((long) i << shift) + base; + } + + /** + * Creates a new AtomicIntegerArray of the given length, with all + * elements initially zero. + * + * @param length the length of the array + */ + public AtomicIntegerArray(int length) { + array = new int[length]; + } + + /** + * Creates a new AtomicIntegerArray with the same length as, and + * all elements copied from, the given array. + * + * @param array the array to copy elements from + * @throws NullPointerException if array is null + */ + public AtomicIntegerArray(int[] array) { + // Visibility guaranteed by final field guarantees + this.array = array.clone(); + } + + /** + * Returns the length of the array. + * + * @return the length of the array + */ + public final int length() { + return array.length; + } + + /** + * Gets the current value at position {@code i}. + * + * @param i the index + * @return the current value + */ + public final int get(int i) { + return getRaw(checkedByteOffset(i)); + } + + private int getRaw(long offset) { + return unsafe.getIntVolatile(array, offset); + } + + /** + * Sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void set(int i, int newValue) { + unsafe.putIntVolatile(array, checkedByteOffset(i), newValue); + } + + /** + * Eventually sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int i, int newValue) { + unsafe.putOrderedInt(array, checkedByteOffset(i), newValue); + } + + /** + * Atomically sets the element at position {@code i} to the given + * value and returns the old value. + * + * @param i the index + * @param newValue the new value + * @return the previous value + */ + public final int getAndSet(int i, int newValue) { + long offset = checkedByteOffset(i); + while (true) { + int current = getRaw(offset); + if (compareAndSetRaw(offset, current, newValue)) + return current; + } + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int i, int expect, int update) { + return compareAndSetRaw(checkedByteOffset(i), expect, update); + } + + private boolean compareAndSetRaw(long offset, int expect, int update) { + return unsafe.compareAndSwapInt(array, offset, expect, update); + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(int i, int expect, int update) { + return compareAndSet(i, expect, update); + } + + /** + * Atomically increments by one the element at index {@code i}. + * + * @param i the index + * @return the previous value + */ + public final int getAndIncrement(int i) { + return getAndAdd(i, 1); + } + + /** + * Atomically decrements by one the element at index {@code i}. + * + * @param i the index + * @return the previous value + */ + public final int getAndDecrement(int i) { + return getAndAdd(i, -1); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the previous value + */ + public final int getAndAdd(int i, int delta) { + long offset = checkedByteOffset(i); + while (true) { + int current = getRaw(offset); + if (compareAndSetRaw(offset, current, current + delta)) + return current; + } + } + + /** + * Atomically increments by one the element at index {@code i}. + * + * @param i the index + * @return the updated value + */ + public final int incrementAndGet(int i) { + return addAndGet(i, 1); + } + + /** + * Atomically decrements by one the element at index {@code i}. + * + * @param i the index + * @return the updated value + */ + public final int decrementAndGet(int i) { + return addAndGet(i, -1); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the updated value + */ + public final int addAndGet(int i, int delta) { + long offset = checkedByteOffset(i); + while (true) { + int current = getRaw(offset); + int next = current + delta; + if (compareAndSetRaw(offset, current, next)) + return next; + } + } + + /** + * Returns the String representation of the current values of array. + * @return the String representation of the current values of array + */ + public String toString() { + int iMax = array.length - 1; + if (iMax == -1) + return "[]"; + + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0; ; i++) { + b.append(getRaw(byteOffset(i))); + if (i == iMax) + return b.append(']').toString(); + b.append(',').append(' '); + } + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicLong.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicLong.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,279 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; +import sun.misc.Unsafe; + +/** + * A {@code long} value that may be updated atomically. See the + * {@link java.util.concurrent.atomic} package specification for + * description of the properties of atomic variables. An + * {@code AtomicLong} is used in applications such as atomically + * incremented sequence numbers, and cannot be used as a replacement + * for a {@link java.lang.Long}. However, this class does extend + * {@code Number} to allow uniform access by tools and utilities that + * deal with numerically-based classes. + * + * @since 1.5 + * @author Doug Lea + */ +public class AtomicLong extends Number implements java.io.Serializable { + private static final long serialVersionUID = 1927816293512124184L; + + // setup to use Unsafe.compareAndSwapLong for updates + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + /** + * Records whether the underlying JVM supports lockless + * compareAndSwap for longs. While the Unsafe.compareAndSwapLong + * method works in either case, some constructions should be + * handled at Java level to avoid locking user-visible locks. + */ + static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); + + /** + * Returns whether underlying JVM supports lockless CompareAndSet + * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS. + */ + private static native boolean VMSupportsCS8(); + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicLong.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile long value; + + /** + * Creates a new AtomicLong with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicLong(long initialValue) { + value = initialValue; + } + + /** + * Creates a new AtomicLong with initial value {@code 0}. + */ + public AtomicLong() { + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final long get() { + return value; + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public final void set(long newValue) { + value = newValue; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(long newValue) { + unsafe.putOrderedLong(this, valueOffset, newValue); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + public final long getAndSet(long newValue) { + while (true) { + long current = get(); + if (compareAndSet(current, newValue)) + return current; + } + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(long expect, long update) { + return unsafe.compareAndSwapLong(this, valueOffset, expect, update); + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(long expect, long update) { + return unsafe.compareAndSwapLong(this, valueOffset, expect, update); + } + + /** + * Atomically increments by one the current value. + * + * @return the previous value + */ + public final long getAndIncrement() { + while (true) { + long current = get(); + long next = current + 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically decrements by one the current value. + * + * @return the previous value + */ + public final long getAndDecrement() { + while (true) { + long current = get(); + long next = current - 1; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the previous value + */ + public final long getAndAdd(long delta) { + while (true) { + long current = get(); + long next = current + delta; + if (compareAndSet(current, next)) + return current; + } + } + + /** + * Atomically increments by one the current value. + * + * @return the updated value + */ + public final long incrementAndGet() { + for (;;) { + long current = get(); + long next = current + 1; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Atomically decrements by one the current value. + * + * @return the updated value + */ + public final long decrementAndGet() { + for (;;) { + long current = get(); + long next = current - 1; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Atomically adds the given value to the current value. + * + * @param delta the value to add + * @return the updated value + */ + public final long addAndGet(long delta) { + for (;;) { + long current = get(); + long next = current + delta; + if (compareAndSet(current, next)) + return next; + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value. + */ + public String toString() { + return Long.toString(get()); + } + + + public int intValue() { + return (int)get(); + } + + public long longValue() { + return get(); + } + + public float floatValue() { + return (float)get(); + } + + public double doubleValue() { + return (double)get(); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicLongArray.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicLongArray.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,284 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; +import sun.misc.Unsafe; +import java.util.*; + +/** + * A {@code long} array in which elements may be updated atomically. + * See the {@link java.util.concurrent.atomic} package specification + * for description of the properties of atomic variables. + * @since 1.5 + * @author Doug Lea + */ +public class AtomicLongArray implements java.io.Serializable { + private static final long serialVersionUID = -2308431214976778248L; + + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final int base = unsafe.arrayBaseOffset(long[].class); + private static final int shift; + private final long[] array; + + static { + int scale = unsafe.arrayIndexScale(long[].class); + if ((scale & (scale - 1)) != 0) + throw new Error("data type scale not a power of two"); + shift = 31 - Integer.numberOfLeadingZeros(scale); + } + + private long checkedByteOffset(int i) { + if (i < 0 || i >= array.length) + throw new IndexOutOfBoundsException("index " + i); + + return byteOffset(i); + } + + private static long byteOffset(int i) { + return ((long) i << shift) + base; + } + + /** + * Creates a new AtomicLongArray of the given length, with all + * elements initially zero. + * + * @param length the length of the array + */ + public AtomicLongArray(int length) { + array = new long[length]; + } + + /** + * Creates a new AtomicLongArray with the same length as, and + * all elements copied from, the given array. + * + * @param array the array to copy elements from + * @throws NullPointerException if array is null + */ + public AtomicLongArray(long[] array) { + // Visibility guaranteed by final field guarantees + this.array = array.clone(); + } + + /** + * Returns the length of the array. + * + * @return the length of the array + */ + public final int length() { + return array.length; + } + + /** + * Gets the current value at position {@code i}. + * + * @param i the index + * @return the current value + */ + public final long get(int i) { + return getRaw(checkedByteOffset(i)); + } + + private long getRaw(long offset) { + return unsafe.getLongVolatile(array, offset); + } + + /** + * Sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void set(int i, long newValue) { + unsafe.putLongVolatile(array, checkedByteOffset(i), newValue); + } + + /** + * Eventually sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int i, long newValue) { + unsafe.putOrderedLong(array, checkedByteOffset(i), newValue); + } + + + /** + * Atomically sets the element at position {@code i} to the given value + * and returns the old value. + * + * @param i the index + * @param newValue the new value + * @return the previous value + */ + public final long getAndSet(int i, long newValue) { + long offset = checkedByteOffset(i); + while (true) { + long current = getRaw(offset); + if (compareAndSetRaw(offset, current, newValue)) + return current; + } + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int i, long expect, long update) { + return compareAndSetRaw(checkedByteOffset(i), expect, update); + } + + private boolean compareAndSetRaw(long offset, long expect, long update) { + return unsafe.compareAndSwapLong(array, offset, expect, update); + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(int i, long expect, long update) { + return compareAndSet(i, expect, update); + } + + /** + * Atomically increments by one the element at index {@code i}. + * + * @param i the index + * @return the previous value + */ + public final long getAndIncrement(int i) { + return getAndAdd(i, 1); + } + + /** + * Atomically decrements by one the element at index {@code i}. + * + * @param i the index + * @return the previous value + */ + public final long getAndDecrement(int i) { + return getAndAdd(i, -1); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the previous value + */ + public final long getAndAdd(int i, long delta) { + long offset = checkedByteOffset(i); + while (true) { + long current = getRaw(offset); + if (compareAndSetRaw(offset, current, current + delta)) + return current; + } + } + + /** + * Atomically increments by one the element at index {@code i}. + * + * @param i the index + * @return the updated value + */ + public final long incrementAndGet(int i) { + return addAndGet(i, 1); + } + + /** + * Atomically decrements by one the element at index {@code i}. + * + * @param i the index + * @return the updated value + */ + public final long decrementAndGet(int i) { + return addAndGet(i, -1); + } + + /** + * Atomically adds the given value to the element at index {@code i}. + * + * @param i the index + * @param delta the value to add + * @return the updated value + */ + public long addAndGet(int i, long delta) { + long offset = checkedByteOffset(i); + while (true) { + long current = getRaw(offset); + long next = current + delta; + if (compareAndSetRaw(offset, current, next)) + return next; + } + } + + /** + * Returns the String representation of the current values of array. + * @return the String representation of the current values of array + */ + public String toString() { + int iMax = array.length - 1; + if (iMax == -1) + return "[]"; + + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0; ; i++) { + b.append(getRaw(byteOffset(i))); + if (i == iMax) + return b.append(']').toString(); + b.append(',').append(' '); + } + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicReference.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicReference.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,155 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; +import sun.misc.Unsafe; + +/** + * An object reference that may be updated atomically. See the {@link + * java.util.concurrent.atomic} package specification for description + * of the properties of atomic variables. + * @since 1.5 + * @author Doug Lea + * @param The type of object referred to by this reference + */ +public class AtomicReference implements java.io.Serializable { + private static final long serialVersionUID = -1848883965231344442L; + + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicReference.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile V value; + + /** + * Creates a new AtomicReference with the given initial value. + * + * @param initialValue the initial value + */ + public AtomicReference(V initialValue) { + value = initialValue; + } + + /** + * Creates a new AtomicReference with null initial value. + */ + public AtomicReference() { + } + + /** + * Gets the current value. + * + * @return the current value + */ + public final V get() { + return value; + } + + /** + * Sets to the given value. + * + * @param newValue the new value + */ + public final void set(V newValue) { + value = newValue; + } + + /** + * Eventually sets to the given value. + * + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(V newValue) { + unsafe.putOrderedObject(this, valueOffset, newValue); + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(V expect, V update) { + return unsafe.compareAndSwapObject(this, valueOffset, expect, update); + } + + /** + * Atomically sets the value to the given updated value + * if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(V expect, V update) { + return unsafe.compareAndSwapObject(this, valueOffset, expect, update); + } + + /** + * Atomically sets to the given value and returns the old value. + * + * @param newValue the new value + * @return the previous value + */ + public final V getAndSet(V newValue) { + while (true) { + V x = get(); + if (compareAndSet(x, newValue)) + return x; + } + } + + /** + * Returns the String representation of the current value. + * @return the String representation of the current value. + */ + public String toString() { + return String.valueOf(get()); + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/AtomicReferenceArray.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,213 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util.concurrent.atomic; +import sun.misc.Unsafe; +import java.util.*; + +/** + * An array of object references in which elements may be updated + * atomically. See the {@link java.util.concurrent.atomic} package + * specification for description of the properties of atomic + * variables. + * @since 1.5 + * @author Doug Lea + * @param The base class of elements held in this array + */ +public class AtomicReferenceArray implements java.io.Serializable { + private static final long serialVersionUID = -6209656149925076980L; + + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final int base = unsafe.arrayBaseOffset(Object[].class); + private static final int shift; + private final Object[] array; + + static { + int scale = unsafe.arrayIndexScale(Object[].class); + if ((scale & (scale - 1)) != 0) + throw new Error("data type scale not a power of two"); + shift = 31 - Integer.numberOfLeadingZeros(scale); + } + + private long checkedByteOffset(int i) { + if (i < 0 || i >= array.length) + throw new IndexOutOfBoundsException("index " + i); + + return byteOffset(i); + } + + private static long byteOffset(int i) { + return ((long) i << shift) + base; + } + + /** + * Creates a new AtomicReferenceArray of the given length, with all + * elements initially null. + * + * @param length the length of the array + */ + public AtomicReferenceArray(int length) { + array = new Object[length]; + } + + /** + * Creates a new AtomicReferenceArray with the same length as, and + * all elements copied from, the given array. + * + * @param array the array to copy elements from + * @throws NullPointerException if array is null + */ + public AtomicReferenceArray(E[] array) { + // Visibility guaranteed by final field guarantees + this.array = array.clone(); + } + + /** + * Returns the length of the array. + * + * @return the length of the array + */ + public final int length() { + return array.length; + } + + /** + * Gets the current value at position {@code i}. + * + * @param i the index + * @return the current value + */ + public final E get(int i) { + return getRaw(checkedByteOffset(i)); + } + + private E getRaw(long offset) { + return (E) unsafe.getObjectVolatile(array, offset); + } + + /** + * Sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + */ + public final void set(int i, E newValue) { + unsafe.putObjectVolatile(array, checkedByteOffset(i), newValue); + } + + /** + * Eventually sets the element at position {@code i} to the given value. + * + * @param i the index + * @param newValue the new value + * @since 1.6 + */ + public final void lazySet(int i, E newValue) { + unsafe.putOrderedObject(array, checkedByteOffset(i), newValue); + } + + + /** + * Atomically sets the element at position {@code i} to the given + * value and returns the old value. + * + * @param i the index + * @param newValue the new value + * @return the previous value + */ + public final E getAndSet(int i, E newValue) { + long offset = checkedByteOffset(i); + while (true) { + E current = (E) getRaw(offset); + if (compareAndSetRaw(offset, current, newValue)) + return current; + } + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. False return indicates that + * the actual value was not equal to the expected value. + */ + public final boolean compareAndSet(int i, E expect, E update) { + return compareAndSetRaw(checkedByteOffset(i), expect, update); + } + + private boolean compareAndSetRaw(long offset, E expect, E update) { + return unsafe.compareAndSwapObject(array, offset, expect, update); + } + + /** + * Atomically sets the element at position {@code i} to the given + * updated value if the current value {@code ==} the expected value. + * + *

May fail spuriously + * and does not provide ordering guarantees, so is only rarely an + * appropriate alternative to {@code compareAndSet}. + * + * @param i the index + * @param expect the expected value + * @param update the new value + * @return true if successful. + */ + public final boolean weakCompareAndSet(int i, E expect, E update) { + return compareAndSet(i, expect, update); + } + + /** + * Returns the String representation of the current values of array. + * @return the String representation of the current values of array + */ + public String toString() { + int iMax = array.length - 1; + if (iMax == -1) + return "[]"; + + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0; ; i++) { + b.append(getRaw(byteOffset(i))); + if (i == iMax) + return b.append(']').toString(); + b.append(',').append(' '); + } + } + +} diff -r 8cb6ebbd4823 -r f194f314cac0 rt/emul/compact/src/main/java/java/util/concurrent/atomic/package-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/emul/compact/src/main/java/java/util/concurrent/atomic/package-info.java Thu Oct 03 15:43:10 2013 +0200 @@ -0,0 +1,199 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * A small toolkit of classes that support lock-free thread-safe + * programming on single variables. In essence, the classes in this + * package extend the notion of {@code volatile} values, fields, and + * array elements to those that also provide an atomic conditional update + * operation of the form: + * + *

+ *   boolean compareAndSet(expectedValue, updateValue);
+ * 
+ * + *

This method (which varies in argument types across different + * classes) atomically sets a variable to the {@code updateValue} if it + * currently holds the {@code expectedValue}, reporting {@code true} on + * success. The classes in this package also contain methods to get and + * unconditionally set values, as well as a weaker conditional atomic + * update operation {@code weakCompareAndSet} described below. + * + *

The specifications of these methods enable implementations to + * employ efficient machine-level atomic instructions that are available + * on contemporary processors. However on some platforms, support may + * entail some form of internal locking. Thus the methods are not + * strictly guaranteed to be non-blocking -- + * a thread may block transiently before performing the operation. + * + *

Instances of classes + * {@link java.util.concurrent.atomic.AtomicBoolean}, + * {@link java.util.concurrent.atomic.AtomicInteger}, + * {@link java.util.concurrent.atomic.AtomicLong}, and + * {@link java.util.concurrent.atomic.AtomicReference} + * each provide access and updates to a single variable of the + * corresponding type. Each class also provides appropriate utility + * methods for that type. For example, classes {@code AtomicLong} and + * {@code AtomicInteger} provide atomic increment methods. One + * application is to generate sequence numbers, as in: + * + *

+ * class Sequencer {
+ *   private final AtomicLong sequenceNumber
+ *     = new AtomicLong(0);
+ *   public long next() {
+ *     return sequenceNumber.getAndIncrement();
+ *   }
+ * }
+ * 
+ * + *

The memory effects for accesses and updates of atomics generally + * follow the rules for volatiles, as stated in section 17.4 of + * The Java™ Language Specification. + * + *

    + * + *
  • {@code get} has the memory effects of reading a + * {@code volatile} variable. + * + *
  • {@code set} has the memory effects of writing (assigning) a + * {@code volatile} variable. + * + *
  • {@code lazySet} has the memory effects of writing (assigning) + * a {@code volatile} variable except that it permits reorderings with + * subsequent (but not previous) memory actions that do not themselves + * impose reordering constraints with ordinary non-{@code volatile} + * writes. Among other usage contexts, {@code lazySet} may apply when + * nulling out, for the sake of garbage collection, a reference that is + * never accessed again. + * + *
  • {@code weakCompareAndSet} atomically reads and conditionally + * writes a variable but does not + * create any happens-before orderings, so provides no guarantees + * with respect to previous or subsequent reads and writes of any + * variables other than the target of the {@code weakCompareAndSet}. + * + *
  • {@code compareAndSet} + * and all other read-and-update operations such as {@code getAndIncrement} + * have the memory effects of both reading and + * writing {@code volatile} variables. + *
+ * + *

In addition to classes representing single values, this package + * contains Updater classes that can be used to obtain + * {@code compareAndSet} operations on any selected {@code volatile} + * field of any selected class. + * + * {@link java.util.concurrent.atomic.AtomicReferenceFieldUpdater}, + * {@link java.util.concurrent.atomic.AtomicIntegerFieldUpdater}, and + * {@link java.util.concurrent.atomic.AtomicLongFieldUpdater} are + * reflection-based utilities that provide access to the associated + * field types. These are mainly of use in atomic data structures in + * which several {@code volatile} fields of the same node (for + * example, the links of a tree node) are independently subject to + * atomic updates. These classes enable greater flexibility in how + * and when to use atomic updates, at the expense of more awkward + * reflection-based setup, less convenient usage, and weaker + * guarantees. + * + *

The + * {@link java.util.concurrent.atomic.AtomicIntegerArray}, + * {@link java.util.concurrent.atomic.AtomicLongArray}, and + * {@link java.util.concurrent.atomic.AtomicReferenceArray} classes + * further extend atomic operation support to arrays of these types. + * These classes are also notable in providing {@code volatile} access + * semantics for their array elements, which is not supported for + * ordinary arrays. + * + * + *

The atomic classes also support method {@code weakCompareAndSet}, + * which has limited applicability. On some platforms, the weak version + * may be more efficient than {@code compareAndSet} in the normal case, + * but differs in that any given invocation of the + * {@code weakCompareAndSet} method may return {@code false} + * spuriously (that is, for no apparent reason). A + * {@code false} return means only that the operation may be retried if + * desired, relying on the guarantee that repeated invocation when the + * variable holds {@code expectedValue} and no other thread is also + * attempting to set the variable will eventually succeed. (Such + * spurious failures may for example be due to memory contention effects + * that are unrelated to whether the expected and current values are + * equal.) Additionally {@code weakCompareAndSet} does not provide + * ordering guarantees that are usually needed for synchronization + * control. However, the method may be useful for updating counters and + * statistics when such updates are unrelated to the other + * happens-before orderings of a program. When a thread sees an update + * to an atomic variable caused by a {@code weakCompareAndSet}, it does + * not necessarily see updates to any other variables that + * occurred before the {@code weakCompareAndSet}. This may be + * acceptable when, for example, updating performance statistics, but + * rarely otherwise. + * + *

The {@link java.util.concurrent.atomic.AtomicMarkableReference} + * class associates a single boolean with a reference. For example, this + * bit might be used inside a data structure to mean that the object + * being referenced has logically been deleted. + * + * The {@link java.util.concurrent.atomic.AtomicStampedReference} + * class associates an integer value with a reference. This may be + * used for example, to represent version numbers corresponding to + * series of updates. + * + *

Atomic classes are designed primarily as building blocks for + * implementing non-blocking data structures and related infrastructure + * classes. The {@code compareAndSet} method is not a general + * replacement for locking. It applies only when critical updates for an + * object are confined to a single variable. + * + *

Atomic classes are not general purpose replacements for + * {@code java.lang.Integer} and related classes. They do not + * define methods such as {@code hashCode} and + * {@code compareTo}. (Because atomic variables are expected to be + * mutated, they are poor choices for hash table keys.) Additionally, + * classes are provided only for those types that are commonly useful in + * intended applications. For example, there is no atomic class for + * representing {@code byte}. In those infrequent cases where you would + * like to do so, you can use an {@code AtomicInteger} to hold + * {@code byte} values, and cast appropriately. + * + * You can also hold floats using + * {@link java.lang.Float#floatToIntBits} and + * {@link java.lang.Float#intBitsToFloat} conversions, and doubles using + * {@link java.lang.Double#doubleToLongBits} and + * {@link java.lang.Double#longBitsToDouble} conversions. + * + * @since 1.5 + */ +package java.util.concurrent.atomic;

    + * + *
  • {@link CoderResult#UNDERFLOW} indicates that as much of the + * input buffer as possible has been encoded. If there is no further + * input then the invoker can proceed to the next step of the + * encoding operation. Otherwise this method + * should be invoked again with further input.

  • + * + *
  • {@link CoderResult#OVERFLOW} indicates that there is + * insufficient space in the output buffer to encode any more characters. + * This method should be invoked again with an output buffer that has + * more {@linkplain Buffer#remaining remaining} bytes. This is + * typically done by draining any encoded bytes from the output + * buffer.

  • + * + *
  • A {@link CoderResult#malformedForLength + * malformed-input} result indicates that a malformed-input + * error has been detected. The malformed characters begin at the input + * buffer's (possibly incremented) position; the number of malformed + * characters may be determined by invoking the result object's {@link + * CoderResult#length() length} method. This case applies only if the + * {@link #onMalformedInput malformed action} of this encoder + * is {@link CodingErrorAction#REPORT}; otherwise the malformed input + * will be ignored or replaced, as requested.

  • + * + *
  • An {@link CoderResult#unmappableForLength + * unmappable-character} result indicates that an + * unmappable-character error has been detected. The characters that + * encode the unmappable character begin at the input buffer's (possibly + * incremented) position; the number of such characters may be determined + * by invoking the result object's {@link CoderResult#length() length} + * method. This case applies only if the {@link #onUnmappableCharacter + * unmappable action} of this encoder is {@link + * CodingErrorAction#REPORT}; otherwise the unmappable character will be + * ignored or replaced, as requested.

  • + * + *